Merge "Change "Storage" permission group label"
diff --git a/Android.bp b/Android.bp
index a7ac094..f6e0104 100644
--- a/Android.bp
+++ b/Android.bp
@@ -409,6 +409,7 @@
 filegroup {
     name: "libincident_aidl",
     srcs: [
+        "core/java/android/os/IIncidentDumpCallback.aidl",
         "core/java/android/os/IIncidentManager.aidl",
         "core/java/android/os/IIncidentReportStatusListener.aidl",
     ],
@@ -491,8 +492,7 @@
         "framework-sdkextensions-stubs-systemapi",
         // TODO(b/146167933): Use framework-statsd-stubs instead.
         "framework-statsd",
-        // TODO(b/140299412): should be framework-wifi-stubs
-        "framework-wifi",
+        "framework-wifi-stubs",
         "ike-stubs",
         // TODO(b/147200698): should be the stub of framework-tethering
         "framework-tethering",
@@ -528,7 +528,8 @@
         "compat-changeid-annotation-processor",
     ],
     static_libs: [
-        "exoplayer2-core"
+        "exoplayer2-core",
+        "android.hardware.wifi-V1.0-java-constants",
     ]
 }
 
@@ -637,6 +638,7 @@
     name: "framework-ike-shared-srcs",
     visibility: ["//frameworks/opt/net/ike"],
     srcs: [
+        "core/java/android/annotation/StringDef.java",
         "core/java/android/net/annotations/PolicyDirection.java",
         "core/java/com/android/internal/util/IState.java",
         "core/java/com/android/internal/util/State.java",
@@ -1124,6 +1126,7 @@
         "core/java/android/util/TimeUtils.java",
         "core/java/com/android/internal/os/SomeArgs.java",
         "core/java/com/android/internal/util/AsyncChannel.java",
+        "core/java/com/android/internal/util/AsyncService.java",
         "core/java/com/android/internal/util/BitwiseInputStream.java",
         "core/java/com/android/internal/util/FastXmlSerializer.java",
         "core/java/com/android/internal/util/HexDump.java",
diff --git a/ApiDocs.bp b/ApiDocs.bp
index e373db6..c40004c 100644
--- a/ApiDocs.bp
+++ b/ApiDocs.bp
@@ -121,8 +121,10 @@
 
 doc_defaults {
     name: "framework-docs-default",
-    libs: framework_docs_only_libs +
-        ["stub-annotations"],
+    libs: framework_docs_only_libs + [
+        "stub-annotations",
+        "unsupportedappusage",
+    ],
     html_dirs: [
         "docs/html",
     ],
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSchema.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSchema.java
index b5d903a..7e5f187 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSchema.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSchema.java
@@ -19,6 +19,8 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import com.google.android.icing.proto.PropertyConfigProto;
 import com.google.android.icing.proto.SchemaProto;
 import com.google.android.icing.proto.SchemaTypeConfigProto;
@@ -70,7 +72,8 @@
      * @hide
      */
     @NonNull
-    SchemaProto getProto() {
+    @VisibleForTesting
+    public SchemaProto getProto() {
         return mProto;
     }
 
@@ -288,7 +291,7 @@
             @NonNull
             public PropertyConfig build() {
                 if (mProtoBuilder.getDataType() == PropertyConfigProto.DataType.Code.UNKNOWN) {
-                    throw new IllegalSchemaException("Missing dataType field");
+                    throw new IllegalSchemaException("Missing field: dataType");
                 }
                 if (mProtoBuilder.getSchemaType().isEmpty()
                         && mProtoBuilder.getDataType()
@@ -299,7 +302,7 @@
                 }
                 if (mProtoBuilder.getCardinality()
                         == PropertyConfigProto.Cardinality.Code.UNKNOWN) {
-                    throw new IllegalSchemaException("Missing cardinality field");
+                    throw new IllegalSchemaException("Missing field: cardinality");
                 }
                 return new PropertyConfig(mProtoBuilder.build());
             }
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/TEST_MAPPING b/apex/appsearch/service/java/com/android/server/appsearch/TEST_MAPPING
index 08811f8..ca5b884 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/TEST_MAPPING
+++ b/apex/appsearch/service/java/com/android/server/appsearch/TEST_MAPPING
@@ -10,6 +10,14 @@
           "include-filter": "com.android.server.appsearch"
         }
       ]
+    },
+    {
+      "name": "FrameworksCoreTests",
+      "options": [
+        {
+           "include-filter": "android.app.appsearch"
+        }
+      ]
     }
   ]
 }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index f6512a6..102e848 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -257,7 +257,7 @@
     private final List<JobRestriction> mJobRestrictions;
 
     private final CountQuotaTracker mQuotaTracker;
-    private static final String QUOTA_TRACKER_SCHEDULE_TAG = ".schedule()";
+    private static final String QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG = ".schedulePersisted()";
     private final PlatformCompat mPlatformCompat;
 
     /**
@@ -522,7 +522,7 @@
         private static final float DEFAULT_CONN_CONGESTION_DELAY_FRAC = 0.5f;
         private static final float DEFAULT_CONN_PREFETCH_RELAX_FRAC = 0.5f;
         private static final boolean DEFAULT_ENABLE_API_QUOTAS = true;
-        private static final int DEFAULT_API_QUOTA_SCHEDULE_COUNT = 500;
+        private static final int DEFAULT_API_QUOTA_SCHEDULE_COUNT = 250;
         private static final long DEFAULT_API_QUOTA_SCHEDULE_WINDOW_MS = MINUTE_IN_MILLIS;
         private static final boolean DEFAULT_API_QUOTA_SCHEDULE_THROW_EXCEPTION = true;
 
@@ -740,6 +740,8 @@
 
             ENABLE_API_QUOTAS = mParser.getBoolean(KEY_ENABLE_API_QUOTAS,
                 DEFAULT_ENABLE_API_QUOTAS);
+            // Set a minimum value on the quota limit so it's not so low that it interferes with
+            // legitimate use cases.
             API_QUOTA_SCHEDULE_COUNT = Math.max(250,
                     mParser.getInt(KEY_API_QUOTA_SCHEDULE_COUNT, DEFAULT_API_QUOTA_SCHEDULE_COUNT));
             API_QUOTA_SCHEDULE_WINDOW_MS = mParser.getDurationMillis(
@@ -1054,44 +1056,48 @@
 
     public int scheduleAsPackage(JobInfo job, JobWorkItem work, int uId, String packageName,
             int userId, String tag) {
-        final String pkg = packageName == null ? job.getService().getPackageName() : packageName;
-        if (!mQuotaTracker.isWithinQuota(userId, pkg, QUOTA_TRACKER_SCHEDULE_TAG)) {
-            Slog.e(TAG, userId + "-" + pkg + " has called schedule() too many times");
-            // TODO(b/145551233): attempt to restrict app
-            if (mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION
-                    && mPlatformCompat.isChangeEnabledByPackageName(
-                            CRASH_ON_EXCEEDED_LIMIT, pkg, userId)) {
-                final boolean isDebuggable;
-                synchronized (mLock) {
-                    if (!mDebuggableApps.containsKey(packageName)) {
-                        try {
-                            final ApplicationInfo appInfo = AppGlobals.getPackageManager()
-                                    .getApplicationInfo(pkg, 0, userId);
-                            if (appInfo != null) {
-                                mDebuggableApps.put(packageName,
-                                        (appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0);
-                            } else {
-                                return JobScheduler.RESULT_FAILURE;
+        if (job.isPersisted()) {
+            // Only limit schedule calls for persisted jobs.
+            final String pkg =
+                    packageName == null ? job.getService().getPackageName() : packageName;
+            if (!mQuotaTracker.isWithinQuota(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG)) {
+                Slog.e(TAG, userId + "-" + pkg + " has called schedule() too many times");
+                // TODO(b/145551233): attempt to restrict app
+                if (mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION
+                        && mPlatformCompat.isChangeEnabledByPackageName(
+                                CRASH_ON_EXCEEDED_LIMIT, pkg, userId)) {
+                    final boolean isDebuggable;
+                    synchronized (mLock) {
+                        if (!mDebuggableApps.containsKey(packageName)) {
+                            try {
+                                final ApplicationInfo appInfo = AppGlobals.getPackageManager()
+                                        .getApplicationInfo(pkg, 0, userId);
+                                if (appInfo != null) {
+                                    mDebuggableApps.put(packageName,
+                                            (appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0);
+                                } else {
+                                    return JobScheduler.RESULT_FAILURE;
+                                }
+                            } catch (RemoteException e) {
+                                throw new RuntimeException(e);
                             }
-                        } catch (RemoteException e) {
-                            throw new RuntimeException(e);
                         }
+                        isDebuggable = mDebuggableApps.get(packageName);
                     }
-                    isDebuggable = mDebuggableApps.get(packageName);
+                    if (isDebuggable) {
+                        // Only throw the exception for debuggable apps.
+                        throw new IllegalStateException(
+                                "schedule()/enqueue() called more than "
+                                        + mQuotaTracker.getLimit(Category.SINGLE_CATEGORY)
+                                        + " times in the past "
+                                        + mQuotaTracker.getWindowSizeMs(Category.SINGLE_CATEGORY)
+                                        + "ms");
+                    }
                 }
-                if (isDebuggable) {
-                    // Only throw the exception for debuggable apps.
-                    throw new IllegalStateException(
-                            "schedule()/enqueue() called more than "
-                                    + mQuotaTracker.getLimit(Category.SINGLE_CATEGORY)
-                                    + " times in the past "
-                                    + mQuotaTracker.getWindowSizeMs(Category.SINGLE_CATEGORY)
-                                    + "ms");
-                }
+                return JobScheduler.RESULT_FAILURE;
             }
-            return JobScheduler.RESULT_FAILURE;
+            mQuotaTracker.noteEvent(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG);
         }
-        mQuotaTracker.noteEvent(userId, pkg, QUOTA_TRACKER_SCHEDULE_TAG);
 
         try {
             if (ActivityManager.getService().isAppStartModeDisabled(uId,
diff --git a/apex/permission/Android.bp b/apex/permission/Android.bp
index 5945fb3..f232e62 100644
--- a/apex/permission/Android.bp
+++ b/apex/permission/Android.bp
@@ -22,6 +22,7 @@
     name: "com.android.permission-defaults",
     key: "com.android.permission.key",
     certificate: ":com.android.permission.certificate",
+    apps: ["PermissionController"],
 }
 
 apex_key {
diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
index bbb87ab..6444b4e 100644
--- a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
+++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
@@ -744,97 +744,6 @@
         return null;
     }
 
-    private void pullKernelWakelock(
-            int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        final KernelWakelockStats wakelockStats =
-                mKernelWakelockReader.readKernelWakelockStats(mTmpWakelockStats);
-        for (Map.Entry<String, KernelWakelockStats.Entry> ent : wakelockStats.entrySet()) {
-            String name = ent.getKey();
-            KernelWakelockStats.Entry kws = ent.getValue();
-            StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-            e.writeString(name);
-            e.writeInt(kws.mCount);
-            e.writeInt(kws.mVersion);
-            e.writeLong(kws.mTotalTime);
-            pulledData.add(e);
-        }
-    }
-
-    private void pullCpuTimePerFreq(
-            int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        for (int cluster = 0; cluster < mKernelCpuSpeedReaders.length; cluster++) {
-            long[] clusterTimeMs = mKernelCpuSpeedReaders[cluster].readAbsolute();
-            if (clusterTimeMs != null) {
-                for (int speed = clusterTimeMs.length - 1; speed >= 0; --speed) {
-                    StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos,
-                            wallClockNanos);
-                    e.writeInt(cluster);
-                    e.writeInt(speed);
-                    e.writeLong(clusterTimeMs[speed]);
-                    pulledData.add(e);
-                }
-            }
-        }
-    }
-
-    private void pullKernelUidCpuTime(
-            int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        mCpuUidUserSysTimeReader.readAbsolute((uid, timesUs) -> {
-            long userTimeUs = timesUs[0], systemTimeUs = timesUs[1];
-            StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-            e.writeInt(uid);
-            e.writeLong(userTimeUs);
-            e.writeLong(systemTimeUs);
-            pulledData.add(e);
-        });
-    }
-
-    private void pullKernelUidCpuFreqTime(
-            int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        mCpuUidFreqTimeReader.readAbsolute((uid, cpuFreqTimeMs) -> {
-            for (int freqIndex = 0; freqIndex < cpuFreqTimeMs.length; ++freqIndex) {
-                if (cpuFreqTimeMs[freqIndex] != 0) {
-                    StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos,
-                            wallClockNanos);
-                    e.writeInt(uid);
-                    e.writeInt(freqIndex);
-                    e.writeLong(cpuFreqTimeMs[freqIndex]);
-                    pulledData.add(e);
-                }
-            }
-        });
-    }
-
-    private void pullKernelUidCpuClusterTime(
-            int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        mCpuUidClusterTimeReader.readAbsolute((uid, cpuClusterTimesMs) -> {
-            for (int i = 0; i < cpuClusterTimesMs.length; i++) {
-                StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos,
-                        wallClockNanos);
-                e.writeInt(uid);
-                e.writeInt(i);
-                e.writeLong(cpuClusterTimesMs[i]);
-                pulledData.add(e);
-            }
-        });
-    }
-
-    private void pullKernelUidCpuActiveTime(
-            int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        mCpuUidActiveTimeReader.readAbsolute((uid, cpuActiveTimesMs) -> {
-            StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-            e.writeInt(uid);
-            e.writeLong((long) cpuActiveTimesMs);
-            pulledData.add(e);
-        });
-    }
-
     private void pullWifiActivityInfo(
             int tagId, long elapsedNanos, long wallClockNanos,
             List<StatsLogEventWrapper> pulledData) {
@@ -918,13 +827,6 @@
         pulledData.add(e);
     }
 
-    private void pullSystemUpTime(int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-        e.writeLong(SystemClock.uptimeMillis());
-        pulledData.add(e);
-    }
-
     private void pullProcessMemoryState(
             int tagId, long elapsedNanos, long wallClockNanos,
             List<StatsLogEventWrapper> pulledData) {
@@ -1502,21 +1404,6 @@
         }
     }
 
-    private void pullBuildInformation(int tagId,
-            long elapsedNanos, long wallClockNanos, List<StatsLogEventWrapper> pulledData) {
-        StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-        e.writeString(Build.FINGERPRINT);
-        e.writeString(Build.BRAND);
-        e.writeString(Build.PRODUCT);
-        e.writeString(Build.DEVICE);
-        e.writeString(Build.VERSION.RELEASE);
-        e.writeString(Build.ID);
-        e.writeString(Build.VERSION.INCREMENTAL);
-        e.writeString(Build.TYPE);
-        e.writeString(Build.TAGS);
-        pulledData.add(e);
-    }
-
     private BatteryStatsHelper getBatteryStatsHelper() {
         if (mBatteryStatsHelper == null) {
             final long callingToken = Binder.clearCallingIdentity();
@@ -2108,36 +1995,6 @@
         long wallClockNanos = SystemClock.currentTimeMicro() * 1000L;
         switch (tagId) {
 
-            case StatsLog.KERNEL_WAKELOCK: {
-                pullKernelWakelock(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-
-            case StatsLog.CPU_TIME_PER_FREQ: {
-                pullCpuTimePerFreq(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-
-            case StatsLog.CPU_TIME_PER_UID: {
-                pullKernelUidCpuTime(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-
-            case StatsLog.CPU_TIME_PER_UID_FREQ: {
-                pullKernelUidCpuFreqTime(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-
-            case StatsLog.CPU_CLUSTER_TIME: {
-                pullKernelUidCpuClusterTime(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-
-            case StatsLog.CPU_ACTIVE_TIME: {
-                pullKernelUidCpuActiveTime(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-
             case StatsLog.WIFI_ACTIVITY_INFO: {
                 pullWifiActivityInfo(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
@@ -2148,11 +2005,6 @@
                 break;
             }
 
-            case StatsLog.SYSTEM_UPTIME: {
-                pullSystemUpTime(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-
             case StatsLog.SYSTEM_ELAPSED_REALTIME: {
                 pullSystemElapsedRealtime(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
@@ -2246,11 +2098,6 @@
                 break;
             }
 
-            case StatsLog.BUILD_INFORMATION: {
-                pullBuildInformation(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-
             case StatsLog.PROCESS_CPU_TIME: {
                 pullProcessCpuTime(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
diff --git a/api/current.txt b/api/current.txt
index ba14b4e6..1f3a4f6 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -386,6 +386,7 @@
     field public static final int canRequestFingerprintGestures = 16844109; // 0x101054d
     field public static final int canRequestTouchExplorationMode = 16843735; // 0x10103d7
     field public static final int canRetrieveWindowContent = 16843653; // 0x1010385
+    field public static final int canTakeScreenshot = 16844304; // 0x1010610
     field public static final int candidatesTextStyleSpans = 16843312; // 0x1010230
     field public static final int cantSaveState = 16844142; // 0x101056e
     field @Deprecated public static final int capitalize = 16843113; // 0x1010169
@@ -2864,6 +2865,7 @@
     method protected void onServiceConnected();
     method public final boolean performGlobalAction(int);
     method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo);
+    method public boolean takeScreenshot(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.graphics.Bitmap>);
     field public static final int GESTURE_SWIPE_DOWN = 2; // 0x2
     field public static final int GESTURE_SWIPE_DOWN_AND_LEFT = 15; // 0xf
     field public static final int GESTURE_SWIPE_DOWN_AND_RIGHT = 16; // 0x10
@@ -2960,6 +2962,7 @@
     field public static final int CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES = 64; // 0x40
     field public static final int CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION = 2; // 0x2
     field public static final int CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT = 1; // 0x1
+    field public static final int CAPABILITY_CAN_TAKE_SCREENSHOT = 128; // 0x80
     field @NonNull public static final android.os.Parcelable.Creator<android.accessibilityservice.AccessibilityServiceInfo> CREATOR;
     field public static final int DEFAULT = 1; // 0x1
     field public static final int FEEDBACK_ALL_MASK = -1; // 0xffffffff
@@ -11418,6 +11421,7 @@
   public final class InstallSourceInfo implements android.os.Parcelable {
     method public int describeContents();
     method @Nullable public String getInitiatingPackageName();
+    method @Nullable public android.content.pm.SigningInfo getInitiatingPackageSigningInfo();
     method @Nullable public String getInstallingPackageName();
     method @Nullable public String getOriginatingPackageName();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -11784,6 +11788,7 @@
     method @NonNull public abstract CharSequence getApplicationLabel(@NonNull android.content.pm.ApplicationInfo);
     method @Nullable public abstract android.graphics.drawable.Drawable getApplicationLogo(@NonNull android.content.pm.ApplicationInfo);
     method @Nullable public abstract android.graphics.drawable.Drawable getApplicationLogo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @NonNull public CharSequence getBackgroundPermissionButtonLabel();
     method @Nullable public abstract android.content.pm.ChangedPackages getChangedPackages(@IntRange(from=0) int);
     method public abstract int getComponentEnabledSetting(@NonNull android.content.ComponentName);
     method @NonNull public abstract android.graphics.drawable.Drawable getDefaultActivityIcon();
@@ -16852,6 +16857,8 @@
     method @Nullable public CharSequence getSubtitle();
     method @NonNull public CharSequence getTitle();
     method public boolean isConfirmationRequired();
+    field public static final int AUTHENTICATION_RESULT_TYPE_BIOMETRIC = 2; // 0x2
+    field public static final int AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL = 1; // 0x1
     field public static final int BIOMETRIC_ACQUIRED_GOOD = 0; // 0x0
     field public static final int BIOMETRIC_ACQUIRED_IMAGER_DIRTY = 3; // 0x3
     field public static final int BIOMETRIC_ACQUIRED_INSUFFICIENT = 2; // 0x2
@@ -16881,6 +16888,7 @@
   }
 
   public static class BiometricPrompt.AuthenticationResult {
+    method public int getAuthenticationType();
     method public android.hardware.biometrics.BiometricPrompt.CryptoObject getCryptoObject();
   }
 
@@ -27138,9 +27146,11 @@
 
   public abstract class VolumeProvider {
     ctor public VolumeProvider(int, int, int);
+    ctor public VolumeProvider(int, int, int, @Nullable String);
     method public final int getCurrentVolume();
     method public final int getMaxVolume();
     method public final int getVolumeControl();
+    method @Nullable public final String getVolumeControlId();
     method public void onAdjustVolume(int);
     method public void onSetVolumeTo(int);
     method public final void setCurrentVolume(int);
@@ -28000,6 +28010,7 @@
     method public int getMaxVolume();
     method public int getPlaybackType();
     method public int getVolumeControl();
+    method @Nullable public String getVolumeControlId();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.media.session.MediaController.PlaybackInfo> CREATOR;
     field public static final int PLAYBACK_TYPE_LOCAL = 1; // 0x1
@@ -29641,7 +29652,7 @@
     method @NonNull public android.net.NetworkRequest.Builder clearCapabilities();
     method public android.net.NetworkRequest.Builder removeCapability(int);
     method public android.net.NetworkRequest.Builder removeTransportType(int);
-    method public android.net.NetworkRequest.Builder setNetworkSpecifier(String);
+    method @Deprecated public android.net.NetworkRequest.Builder setNetworkSpecifier(String);
     method public android.net.NetworkRequest.Builder setNetworkSpecifier(android.net.NetworkSpecifier);
   }
 
@@ -29740,6 +29751,19 @@
     method public void onStopped();
   }
 
+  public final class TelephonyNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getSubscriptionId();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.TelephonyNetworkSpecifier> CREATOR;
+  }
+
+  public static final class TelephonyNetworkSpecifier.Builder {
+    ctor public TelephonyNetworkSpecifier.Builder();
+    method @NonNull public android.net.TelephonyNetworkSpecifier build();
+    method @NonNull public android.net.TelephonyNetworkSpecifier.Builder setSubscriptionId(int);
+  }
+
   public class TrafficStats {
     ctor public TrafficStats();
     method public static void clearThreadStatsTag();
@@ -30497,6 +30521,11 @@
     field @Deprecated public static final String[] strings;
   }
 
+  @Deprecated public static class WifiConfiguration.SuiteBCipher {
+    field @Deprecated public static final int ECDHE_ECDSA = 0; // 0x0
+    field @Deprecated public static final int ECDHE_RSA = 1; // 0x1
+  }
+
   public class WifiEnterpriseConfig implements android.os.Parcelable {
     ctor public WifiEnterpriseConfig();
     ctor public WifiEnterpriseConfig(android.net.wifi.WifiEnterpriseConfig);
@@ -39517,7 +39546,8 @@
     field public static final String DISPLAY_NAME = "_display_name";
     field public static final String DOCUMENT_ID = "document_id";
     field public static final String DURATION = "duration";
-    field public static final String GENERATION = "generation";
+    field public static final String GENERATION_ADDED = "generation_added";
+    field public static final String GENERATION_MODIFIED = "generation_modified";
     field public static final String GENRE = "genre";
     field public static final String HEIGHT = "height";
     field public static final String INSTANCE_ID = "instance_id";
@@ -44050,6 +44080,7 @@
     method public java.util.List<android.telecom.Call> getChildren();
     method public java.util.List<android.telecom.Call> getConferenceableCalls();
     method public android.telecom.Call.Details getDetails();
+    method @Nullable public android.telecom.Call getGenericConferenceActiveChildCall();
     method public android.telecom.Call getParent();
     method public String getRemainingPostDialSequence();
     method @Nullable public android.telecom.Call.RttCall getRttCall();
@@ -44133,6 +44164,7 @@
     method public int getCallerDisplayNamePresentation();
     method public int getCallerNumberVerificationStatus();
     method public final long getConnectTimeMillis();
+    method @Nullable public String getContactDisplayName();
     method public long getCreationTimeMillis();
     method public android.telecom.DisconnectCause getDisconnectCause();
     method public android.os.Bundle getExtras();
@@ -58897,7 +58929,7 @@
     method @android.view.ViewDebug.ExportedProperty public CharSequence getFormat24Hour();
     method public String getTimeZone();
     method public boolean is24HourModeEnabled();
-    method public void refresh();
+    method public void refreshTime();
     method public void setFormat12Hour(CharSequence);
     method public void setFormat24Hour(CharSequence);
     method public void setTimeZone(String);
@@ -59212,6 +59244,7 @@
 
   public class Toast {
     ctor public Toast(android.content.Context);
+    method public void addCallback(@NonNull android.widget.Toast.Callback);
     method public void cancel();
     method public int getDuration();
     method public int getGravity();
@@ -59222,6 +59255,7 @@
     method public int getYOffset();
     method public static android.widget.Toast makeText(android.content.Context, CharSequence, int);
     method public static android.widget.Toast makeText(android.content.Context, @StringRes int, int) throws android.content.res.Resources.NotFoundException;
+    method public void removeCallback(@NonNull android.widget.Toast.Callback);
     method public void setDuration(int);
     method public void setGravity(int, int, int);
     method public void setMargin(float, float);
@@ -59233,6 +59267,12 @@
     field public static final int LENGTH_SHORT = 0; // 0x0
   }
 
+  public abstract static class Toast.Callback {
+    ctor public Toast.Callback();
+    method public void onToastHidden();
+    method public void onToastShown();
+  }
+
   public class ToggleButton extends android.widget.CompoundButton {
     ctor public ToggleButton(android.content.Context, android.util.AttributeSet, int, int);
     ctor public ToggleButton(android.content.Context, android.util.AttributeSet, int);
diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt
index 0a67065..c8253a0 100644
--- a/api/module-lib-current.txt
+++ b/api/module-lib-current.txt
@@ -79,3 +79,48 @@
 
 }
 
+package android.timezone {
+
+  public final class CountryTimeZones {
+    method @Nullable public android.icu.util.TimeZone getDefaultTimeZone();
+    method @Nullable public String getDefaultTimeZoneId();
+    method @NonNull public java.util.List<android.timezone.CountryTimeZones.TimeZoneMapping> getEffectiveTimeZoneMappingsAt(long);
+    method public boolean hasUtcZone(long);
+    method public boolean isDefaultTimeZoneBoosted();
+    method public boolean isForCountryCode(@NonNull String);
+    method @Nullable public android.timezone.CountryTimeZones.OffsetResult lookupByOffsetWithBias(int, @Nullable Boolean, @Nullable Integer, long, @Nullable android.icu.util.TimeZone);
+  }
+
+  public static final class CountryTimeZones.OffsetResult {
+    ctor public CountryTimeZones.OffsetResult(@NonNull android.icu.util.TimeZone, boolean);
+    method @NonNull public android.icu.util.TimeZone getTimeZone();
+    method public boolean isOnlyMatch();
+  }
+
+  public static final class CountryTimeZones.TimeZoneMapping {
+    method @Nullable public android.icu.util.TimeZone getTimeZone();
+    method @NonNull public String getTimeZoneId();
+  }
+
+  public class TelephonyLookup {
+    method @NonNull public static android.timezone.TelephonyLookup getInstance();
+    method @Nullable public android.timezone.TelephonyNetworkFinder getTelephonyNetworkFinder();
+  }
+
+  public class TelephonyNetwork {
+    method @NonNull public String getCountryIsoCode();
+    method @NonNull public String getMcc();
+    method @NonNull public String getMnc();
+  }
+
+  public class TelephonyNetworkFinder {
+    method @Nullable public android.timezone.TelephonyNetwork findNetworkByMccMnc(@NonNull String, @NonNull String);
+  }
+
+  public final class TimeZoneFinder {
+    method @NonNull public static android.timezone.TimeZoneFinder getInstance();
+    method @Nullable public android.timezone.CountryTimeZones lookupCountryTimeZones(@NonNull String);
+  }
+
+}
+
diff --git a/api/system-current.txt b/api/system-current.txt
index 71d1a38..09684d8 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -791,6 +791,7 @@
 package android.app.admin {
 
   public class DevicePolicyManager {
+    method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean getBluetoothContactSharingDisabled(@NonNull android.os.UserHandle);
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getDeviceOwner();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.content.ComponentName getDeviceOwnerComponentOnAnyUser();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getDeviceOwnerNameOnAnyUser();
@@ -1037,6 +1038,7 @@
     field public static final int AGENT_ERROR = -1003; // 0xfffffc15
     field public static final int AGENT_UNKNOWN = -1004; // 0xfffffc14
     field public static final String EXTRA_TRANSPORT_REGISTRATION = "android.app.backup.extra.TRANSPORT_REGISTRATION";
+    field public static final int FLAG_DATA_NOT_CHANGED = 8; // 0x8
     field public static final int FLAG_INCREMENTAL = 2; // 0x2
     field public static final int FLAG_NON_INCREMENTAL = 4; // 0x4
     field public static final int FLAG_USER_INITIATED = 1; // 0x1
@@ -1703,7 +1705,7 @@
     method public abstract void sendBroadcast(android.content.Intent, @Nullable String, @Nullable android.os.Bundle);
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public abstract void sendBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle, @Nullable String, @Nullable android.os.Bundle);
     method public abstract void sendOrderedBroadcast(@NonNull android.content.Intent, @Nullable String, @Nullable android.os.Bundle, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle);
-    method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public void startActivityAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.os.UserHandle);
+    method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public void startActivityAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.os.UserHandle);
     field public static final String APP_INTEGRITY_SERVICE = "app_integrity";
     field public static final String APP_PREDICTION_SERVICE = "app_prediction";
     field public static final String BACKUP_SERVICE = "backup";
@@ -4802,6 +4804,7 @@
     method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported();
     method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public int registerNetworkProvider(@NonNull android.net.NetworkProvider);
     method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEventCallback);
+    method @Deprecated public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, int, int, @NonNull android.os.Handler);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_AIRPLANE_MODE, android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void setAirplaneMode(boolean);
     method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public boolean shouldAvoidBadWifi();
     method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void startCaptivePortalApp(@NonNull android.net.Network, @NonNull android.os.Bundle);
@@ -4819,6 +4822,7 @@
     field public static final int TETHER_ERROR_NO_ERROR = 0; // 0x0
     field public static final int TETHER_ERROR_PROVISION_FAILED = 11; // 0xb
     field public static final int TYPE_NONE = -1; // 0xffffffff
+    field @Deprecated public static final int TYPE_WIFI_P2P = 13; // 0xd
   }
 
   public abstract static class ConnectivityManager.OnStartTetheringCallback {
@@ -4967,6 +4971,27 @@
     field public final int netId;
   }
 
+  public abstract class NetworkAgent {
+    method public void onAddKeepalivePacketFilter(int, @NonNull android.net.KeepalivePacketData);
+    method public void onAutomaticReconnectDisabled();
+    method public void onBandwidthUpdateRequested();
+    method public void onNetworkUnwanted();
+    method public void onRemoveKeepalivePacketFilter(int);
+    method public void onSaveAcceptUnvalidated(boolean);
+    method public void onSignalStrengthThresholdsUpdated(@NonNull int[]);
+    method public void onStartSocketKeepalive(int, int, @NonNull android.net.KeepalivePacketData);
+    method public void onStopSocketKeepalive(int);
+    method public void onValidationStatus(int, @Nullable String);
+    method public void sendLinkProperties(@NonNull android.net.LinkProperties);
+    method public void sendNetworkCapabilities(@NonNull android.net.NetworkCapabilities);
+    method public void sendNetworkScore(int);
+    method public void sendSocketKeepaliveEvent(int, int);
+    field public static final int VALIDATION_STATUS_NOT_VALID = 2; // 0x2
+    field public static final int VALIDATION_STATUS_VALID = 1; // 0x1
+    field @NonNull public final android.net.Network network;
+    field public final int providerId;
+  }
+
   public final class NetworkAgentConfig implements android.os.Parcelable {
     method public int describeContents();
     method @Nullable public String getSubscriberId();
@@ -5022,6 +5047,10 @@
     method public abstract void onRequestScores(android.net.NetworkKey[]);
   }
 
+  public class NetworkRequest implements android.os.Parcelable {
+    method public boolean satisfiedBy(@Nullable android.net.NetworkCapabilities);
+  }
+
   public static class NetworkRequest.Builder {
     method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkRequest.Builder setSignalStrength(int);
   }
@@ -5128,6 +5157,10 @@
     field public final android.net.RssiCurve rssiCurve;
   }
 
+  public abstract class SocketKeepalive implements java.lang.AutoCloseable {
+    field public static final int SUCCESS = 0; // 0x0
+  }
+
   public final class StaticIpConfiguration implements android.os.Parcelable {
     ctor public StaticIpConfiguration();
     ctor public StaticIpConfiguration(@Nullable android.net.StaticIpConfiguration);
@@ -5161,6 +5194,10 @@
     field @NonNull public final String specifier;
   }
 
+  public final class TelephonyNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
+    method public boolean satisfiedBy(android.net.NetworkSpecifier);
+  }
+
   public class TrafficStats {
     method public static void setThreadStatsTagApp();
     method public static void setThreadStatsTagBackup();
@@ -10311,6 +10348,7 @@
   public class ServiceState implements android.os.Parcelable {
     method @NonNull public android.telephony.ServiceState createLocationInfoSanitizedCopy(boolean);
     method public void fillInNotifierBundle(@NonNull android.os.Bundle);
+    method public int getDataNetworkType();
     method public int getDataRegistrationState();
     method @Nullable public android.telephony.NetworkRegistrationInfo getNetworkRegistrationInfo(int, int);
     method @NonNull public java.util.List<android.telephony.NetworkRegistrationInfo> getNetworkRegistrationInfoList();
@@ -10567,6 +10605,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String getCdmaMin();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String getCdmaMin(int);
     method public String getCdmaPrlVersion();
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getCdmaRoamingMode();
     method public int getCurrentPhoneType();
     method public int getCurrentPhoneType(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getDataActivationState();
@@ -10640,6 +10679,8 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setAlwaysAllowMmsData(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCarrierDataEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setCarrierRestrictionRules(@NonNull android.telephony.CarrierRestrictionRules);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setCdmaRoamingMode(int);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setCdmaSubscriptionMode(int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataActivationState(int);
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(int, boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataRoamingEnabled(boolean);
@@ -10683,6 +10724,9 @@
     field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
     field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0
     field public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; // 0xffffffff
+    field public static final int CDMA_SUBSCRIPTION_NV = 1; // 0x1
+    field public static final int CDMA_SUBSCRIPTION_RUIM_SIM = 0; // 0x0
+    field public static final int CDMA_SUBSCRIPTION_UNKNOWN = -1; // 0xffffffff
     field public static final String EXTRA_ANOMALY_DESCRIPTION = "android.telephony.extra.ANOMALY_DESCRIPTION";
     field public static final String EXTRA_ANOMALY_ID = "android.telephony.extra.ANOMALY_ID";
     field @Deprecated public static final String EXTRA_APN_PROTOCOL = "apnProto";
@@ -11596,6 +11640,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public boolean getProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int);
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public String getProvisioningStringValue(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public boolean getRcsProvisioningStatusForCapability(int);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyRcsAutoConfigurationReceived(@NonNull byte[], boolean);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerProvisioningChangedCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.Callback) throws android.telephony.ims.ImsException;
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningIntValue(int, int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int, boolean);
@@ -11860,6 +11905,7 @@
     method public String getConfigString(int);
     method public final void notifyProvisionedValueChanged(int, int);
     method public final void notifyProvisionedValueChanged(int, String);
+    method public void notifyRcsAutoConfigurationReceived(@NonNull byte[], boolean);
     method public int setConfig(int, int);
     method public int setConfig(int, String);
     field public static final int CONFIG_RESULT_FAILED = 1; // 0x1
@@ -12237,6 +12283,12 @@
     method public void onJsResultComplete(android.webkit.JsResult);
   }
 
+  public interface PacProcessor {
+    method @NonNull public static android.webkit.PacProcessor getInstance();
+    method @Nullable public String makeProxyRequest(@NonNull String);
+    method public boolean setProxyScript(@NonNull String);
+  }
+
   public class SslErrorHandler extends android.os.Handler {
     ctor public SslErrorHandler();
   }
@@ -12371,6 +12423,7 @@
     method public android.webkit.WebViewProvider createWebView(android.webkit.WebView, android.webkit.WebView.PrivateAccess);
     method public android.webkit.CookieManager getCookieManager();
     method public android.webkit.GeolocationPermissions getGeolocationPermissions();
+    method @NonNull public default android.webkit.PacProcessor getPacProcessor();
     method public android.webkit.ServiceWorkerController getServiceWorkerController();
     method public android.webkit.WebViewFactoryProvider.Statics getStatics();
     method @Deprecated public android.webkit.TokenBindingService getTokenBindingService();
diff --git a/api/test-current.txt b/api/test-current.txt
index 1db4c9b..32959c9 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -471,7 +471,10 @@
   }
 
   public class WallpaperManager {
+    method @Nullable public android.graphics.Bitmap getBitmap();
     method @RequiresPermission("android.permission.SET_WALLPAPER_COMPONENT") public boolean setWallpaperComponent(android.content.ComponentName);
+    method public boolean shouldEnableWideColorGamut();
+    method @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public boolean wallpaperSupportsWcg(int);
   }
 
   public class WindowConfiguration implements java.lang.Comparable<android.app.WindowConfiguration> android.os.Parcelable {
@@ -3264,6 +3267,7 @@
 
   public class ServiceState implements android.os.Parcelable {
     method public void addNetworkRegistrationInfo(android.telephony.NetworkRegistrationInfo);
+    method public int getDataNetworkType();
     method public void setCdmaSystemAndNetworkId(int, int);
     method public void setCellBandwidths(int[]);
     method public void setChannelNumber(int);
@@ -3827,6 +3831,7 @@
     method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") @WorkerThread public boolean getProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int);
     method @Nullable @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") @WorkerThread public String getProvisioningStringValue(int);
     method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") @WorkerThread public boolean getRcsProvisioningStatusForCapability(int);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyRcsAutoConfigurationReceived(@NonNull byte[], boolean);
     method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void registerProvisioningChangedCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.Callback) throws android.telephony.ims.ImsException;
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningIntValue(int, int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int, boolean);
@@ -4050,6 +4055,7 @@
     method public String getConfigString(int);
     method public final void notifyProvisionedValueChanged(int, int);
     method public final void notifyProvisionedValueChanged(int, String);
+    method public void notifyRcsAutoConfigurationReceived(@NonNull byte[], boolean);
     method public int setConfig(int, int);
     method public int setConfig(int, String);
     field public static final int CONFIG_RESULT_FAILED = 1; // 0x1
@@ -4379,6 +4385,7 @@
   }
 
   public final class Display {
+    method @NonNull public android.graphics.ColorSpace[] getSupportedWideColorGamut();
     method public boolean hasAccess(int);
   }
 
@@ -4496,6 +4503,8 @@
   }
 
   public class AccessibilityNodeInfo implements android.os.Parcelable {
+    method public void addChild(@NonNull android.os.IBinder);
+    method public void setLeashedParent(@Nullable android.os.IBinder, int);
     method public static void setNumInstancesInUseCounter(java.util.concurrent.atomic.AtomicInteger);
     method public void writeToParcelNoRecycle(android.os.Parcel, int);
   }
diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp
index cfd77c2..62312d1 100644
--- a/cmds/incidentd/src/IncidentService.cpp
+++ b/cmds/incidentd/src/IncidentService.cpp
@@ -123,14 +123,17 @@
 
 // ================================================================================
 ReportHandler::ReportHandler(const sp<WorkDirectory>& workDirectory,
-            const sp<Broadcaster>& broadcaster, const sp<Looper>& handlerLooper,
-            const sp<Throttler>& throttler)
+                             const sp<Broadcaster>& broadcaster,
+                             const sp<Looper>& handlerLooper,
+                             const sp<Throttler>& throttler,
+                             const vector<BringYourOwnSection*>& registeredSections)
         :mLock(),
          mWorkDirectory(workDirectory),
          mBroadcaster(broadcaster),
          mHandlerLooper(handlerLooper),
          mBacklogDelay(DEFAULT_DELAY_NS),
          mThrottler(throttler),
+         mRegisteredSections(registeredSections),
          mBatch(new ReportBatch()) {
 }
 
@@ -185,7 +188,7 @@
         return;
     }
 
-    sp<Reporter> reporter = new Reporter(mWorkDirectory, batch);
+    sp<Reporter> reporter = new Reporter(mWorkDirectory, batch, mRegisteredSections);
 
     // Take the report, which might take a while. More requests might queue
     // up while we're doing this, and we'll handle them in their next batch.
@@ -237,7 +240,7 @@
     mWorkDirectory = new WorkDirectory();
     mBroadcaster = new Broadcaster(mWorkDirectory);
     mHandler = new ReportHandler(mWorkDirectory, mBroadcaster, handlerLooper,
-            mThrottler);
+            mThrottler, mRegisteredSections);
     mBroadcaster->setHandler(mHandler);
 }
 
@@ -327,6 +330,11 @@
             incidentArgs.addSection(id);
         }
     }
+    for (const Section* section : mRegisteredSections) {
+        if (!section_requires_specific_mention(section->id)) {
+            incidentArgs.addSection(section->id);
+        }
+    }
 
     // The ReportRequest takes ownership of the fd, so we need to dup it.
     int fd = dup(stream.get());
@@ -339,6 +347,45 @@
     return Status::ok();
 }
 
+Status IncidentService::registerSection(const int id, const String16& name16,
+        const sp<IIncidentDumpCallback>& callback) {
+    const char* name = String8(name16).c_str();
+    ALOGI("Register section %d: %s", id, name);
+    if (callback == nullptr) {
+        return Status::fromExceptionCode(Status::EX_NULL_POINTER);
+    }
+    const uid_t callingUid = IPCThreadState::self()->getCallingUid();
+    for (int i = 0; i < mRegisteredSections.size(); i++) {
+        if (mRegisteredSections.at(i)->id == id) {
+            if (mRegisteredSections.at(i)->uid != callingUid) {
+                ALOGW("Error registering section %d: calling uid does not match", id);
+                return Status::fromExceptionCode(Status::EX_SECURITY);
+            }
+            mRegisteredSections.at(i) = new BringYourOwnSection(id, name, callingUid, callback);
+            return Status::ok();
+        }
+    }
+    mRegisteredSections.push_back(new BringYourOwnSection(id, name, callingUid, callback));
+    return Status::ok();
+}
+
+Status IncidentService::unregisterSection(const int id) {
+    ALOGI("Unregister section %d", id);
+    uid_t callingUid = IPCThreadState::self()->getCallingUid();
+    for (auto it = mRegisteredSections.begin(); it != mRegisteredSections.end(); it++) {
+        if ((*it)->id == id) {
+            if ((*it)->uid != callingUid) {
+                ALOGW("Error unregistering section %d: calling uid does not match", id);
+                return Status::fromExceptionCode(Status::EX_SECURITY);
+            }
+            mRegisteredSections.erase(it);
+            return Status::ok();
+        }
+    }
+    ALOGW("Section %d not found", id);
+    return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE);
+}
+
 Status IncidentService::systemRunning() {
     if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) {
         return Status::fromExceptionCode(Status::EX_SECURITY,
diff --git a/cmds/incidentd/src/IncidentService.h b/cmds/incidentd/src/IncidentService.h
index b2c7f23..49fc566 100644
--- a/cmds/incidentd/src/IncidentService.h
+++ b/cmds/incidentd/src/IncidentService.h
@@ -40,12 +40,16 @@
 using namespace android::binder;
 using namespace android::os;
 
+class BringYourOwnSection;
+
 // ================================================================================
 class ReportHandler : public MessageHandler {
 public:
     ReportHandler(const sp<WorkDirectory>& workDirectory,
-            const sp<Broadcaster>& broadcaster, const sp<Looper>& handlerLooper,
-            const sp<Throttler>& throttler);
+                  const sp<Broadcaster>& broadcaster,
+                  const sp<Looper>& handlerLooper,
+                  const sp<Throttler>& throttler,
+                  const vector<BringYourOwnSection*>& registeredSections);
     virtual ~ReportHandler();
 
     virtual void handleMessage(const Message& message);
@@ -79,6 +83,8 @@
     nsecs_t mBacklogDelay;
     sp<Throttler> mThrottler;
 
+    const vector<BringYourOwnSection*>& mRegisteredSections;
+
     sp<ReportBatch> mBatch;
 
     /**
@@ -126,6 +132,11 @@
     virtual Status reportIncidentToDumpstate(unique_fd stream,
             const sp<IIncidentReportStatusListener>& listener);
 
+    virtual Status registerSection(int id, const String16& name,
+            const sp<IIncidentDumpCallback>& callback);
+
+    virtual Status unregisterSection(int id);
+
     virtual Status systemRunning();
 
     virtual Status getIncidentReportList(const String16& pkg, const String16& cls,
@@ -149,6 +160,7 @@
     sp<Broadcaster> mBroadcaster;
     sp<ReportHandler> mHandler;
     sp<Throttler> mThrottler;
+    vector<BringYourOwnSection*> mRegisteredSections;
 
     /**
      * Commands print out help.
diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp
index 02b6bbe..aa40f85 100644
--- a/cmds/incidentd/src/Reporter.cpp
+++ b/cmds/incidentd/src/Reporter.cpp
@@ -364,7 +364,6 @@
     mSectionBufferSuccess = false;
     mHadError = false;
     mSectionErrors.clear();
-    
 }
 
 void ReportWriter::setSectionStats(const FdBuffer& buffer) {
@@ -470,10 +469,13 @@
 
 
 // ================================================================================
-Reporter::Reporter(const sp<WorkDirectory>& workDirectory, const sp<ReportBatch>& batch)
+Reporter::Reporter(const sp<WorkDirectory>& workDirectory,
+                   const sp<ReportBatch>& batch,
+                   const vector<BringYourOwnSection*>& registeredSections)
         :mWorkDirectory(workDirectory),
          mWriter(batch),
-         mBatch(batch) {
+         mBatch(batch),
+         mRegisteredSections(registeredSections) {
 }
 
 Reporter::~Reporter() {
@@ -580,50 +582,15 @@
     // For each of the report fields, see if we need it, and if so, execute the command
     // and report to those that care that we're doing it.
     for (const Section** section = SECTION_LIST; *section; section++) {
-        const int sectionId = (*section)->id;
-
-        // If nobody wants this section, skip it.
-        if (!mBatch->containsSection(sectionId)) {
-            continue;
-        }
-
-        ALOGD("Start incident report section %d '%s'", sectionId, (*section)->name.string());
-        IncidentMetadata::SectionStats* sectionMetadata = metadata.add_sections();
-
-        // Notify listener of starting
-        mBatch->forEachListener(sectionId, [sectionId](const auto& listener) {
-            listener->onReportSectionStatus(
-                    sectionId, IIncidentReportStatusListener::STATUS_STARTING);
-        });
-
-        // Go get the data and write it into the file descriptors.
-        mWriter.startSection(sectionId);
-        err = (*section)->Execute(&mWriter);
-        mWriter.endSection(sectionMetadata);
-
-        // Sections returning errors are fatal. Most errors should not be fatal.
-        if (err != NO_ERROR) {
-            mWriter.error((*section), err, "Section failed. Stopping report.");
+        if (execute_section(*section, &metadata, reportByteSize) != NO_ERROR) {
             goto DONE;
         }
+    }
 
-        // The returned max data size is used for throttling too many incident reports.
-        (*reportByteSize) += sectionMetadata->report_size_bytes();
-
-        // For any requests that failed during this section, remove them now.  We do this
-        // before calling back about section finished, so listeners do not erroniously get the
-        // impression that the section succeeded.  But we do it here instead of inside
-        // writeSection so that the callback is done from a known context and not from the
-        // bowels of a section, where changing the batch could cause odd errors.
-        cancel_and_remove_failed_requests();
-
-        // Notify listener of finishing
-        mBatch->forEachListener(sectionId, [sectionId](const auto& listener) {
-                listener->onReportSectionStatus(
-                        sectionId, IIncidentReportStatusListener::STATUS_FINISHED);
-        });
-
-        ALOGD("Finish incident report section %d '%s'", sectionId, (*section)->name.string());
+    for (const Section* section : mRegisteredSections) {
+        if (execute_section(section, &metadata, reportByteSize) != NO_ERROR) {
+            goto DONE;
+        }
     }
 
 DONE:
@@ -681,6 +648,55 @@
     ALOGI("Done taking incident report err=%s", strerror(-err));
 }
 
+status_t Reporter::execute_section(const Section* section, IncidentMetadata* metadata,
+        size_t* reportByteSize) {
+    const int sectionId = section->id;
+
+    // If nobody wants this section, skip it.
+    if (!mBatch->containsSection(sectionId)) {
+        return NO_ERROR;
+    }
+
+    ALOGD("Start incident report section %d '%s'", sectionId, section->name.string());
+    IncidentMetadata::SectionStats* sectionMetadata = metadata->add_sections();
+
+    // Notify listener of starting
+    mBatch->forEachListener(sectionId, [sectionId](const auto& listener) {
+        listener->onReportSectionStatus(
+                sectionId, IIncidentReportStatusListener::STATUS_STARTING);
+    });
+
+    // Go get the data and write it into the file descriptors.
+    mWriter.startSection(sectionId);
+    status_t err = section->Execute(&mWriter);
+    mWriter.endSection(sectionMetadata);
+
+    // Sections returning errors are fatal. Most errors should not be fatal.
+    if (err != NO_ERROR) {
+        mWriter.error(section, err, "Section failed. Stopping report.");
+        return err;
+    }
+
+    // The returned max data size is used for throttling too many incident reports.
+    (*reportByteSize) += sectionMetadata->report_size_bytes();
+
+    // For any requests that failed during this section, remove them now.  We do this
+    // before calling back about section finished, so listeners do not erroniously get the
+    // impression that the section succeeded.  But we do it here instead of inside
+    // writeSection so that the callback is done from a known context and not from the
+    // bowels of a section, where changing the batch could cause odd errors.
+    cancel_and_remove_failed_requests();
+
+    // Notify listener of finishing
+    mBatch->forEachListener(sectionId, [sectionId](const auto& listener) {
+            listener->onReportSectionStatus(
+                    sectionId, IIncidentReportStatusListener::STATUS_FINISHED);
+    });
+
+    ALOGD("Finish incident report section %d '%s'", sectionId, section->name.string());
+    return NO_ERROR;
+}
+
 void Reporter::cancel_and_remove_failed_requests() {
     // Handle a failure in the persisted file
     if (mPersistedFile != nullptr) {
diff --git a/cmds/incidentd/src/Reporter.h b/cmds/incidentd/src/Reporter.h
index fb3961a..cbc8b13 100644
--- a/cmds/incidentd/src/Reporter.h
+++ b/cmds/incidentd/src/Reporter.h
@@ -21,6 +21,7 @@
 #include "frameworks/base/core/proto/android/os/metadata.pb.h"
 #include <android/content/ComponentName.h>
 #include <android/os/IIncidentReportStatusListener.h>
+#include <android/os/IIncidentDumpCallback.h>
 #include <android/os/IncidentReportArgs.h>
 #include <android/util/protobuf.h>
 
@@ -39,6 +40,7 @@
 using namespace android::content;
 using namespace android::os;
 
+class BringYourOwnSection;
 class Section;
 
 // ================================================================================
@@ -122,7 +124,7 @@
     void forEachStreamingRequest(const function<void (const sp<ReportRequest>&)>& func);
 
     /**
-     * Call func(request) for each file descriptor that has 
+     * Call func(request) for each file descriptor.
      */
     void forEachFd(int sectionId, const function<void (const sp<ReportRequest>&)>& func);
 
@@ -251,7 +253,9 @@
 // ================================================================================
 class Reporter : public virtual RefBase {
 public:
-    Reporter(const sp<WorkDirectory>& workDirectory, const sp<ReportBatch>& batch);
+    Reporter(const sp<WorkDirectory>& workDirectory,
+             const sp<ReportBatch>& batch,
+             const vector<BringYourOwnSection*>& registeredSections);
 
     virtual ~Reporter();
 
@@ -263,6 +267,10 @@
     ReportWriter mWriter;
     sp<ReportBatch> mBatch;
     sp<ReportFile> mPersistedFile;
+    const vector<BringYourOwnSection*>& mRegisteredSections;
+
+    status_t execute_section(const Section* section, IncidentMetadata* metadata,
+        size_t* reportByteSize);
 
     void cancel_and_remove_failed_requests();
 };
diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp
index c9277a5..2229e1c 100644
--- a/cmds/incidentd/src/Section.cpp
+++ b/cmds/incidentd/src/Section.cpp
@@ -267,7 +267,7 @@
     signal(SIGPIPE, sigpipe_handler);
 
     WorkerThreadData* data = (WorkerThreadData*)cookie;
-    status_t err = data->section->BlockingCall(data->pipe.writeFd().get());
+    status_t err = data->section->BlockingCall(data->pipe.writeFd());
 
     {
         unique_lock<mutex> lock(data->lock);
@@ -458,7 +458,7 @@
 
 DumpsysSection::~DumpsysSection() {}
 
-status_t DumpsysSection::BlockingCall(int pipeWriteFd) const {
+status_t DumpsysSection::BlockingCall(unique_fd& pipeWriteFd) const {
     // checkService won't wait for the service to show up like getService will.
     sp<IBinder> service = defaultServiceManager()->checkService(mService);
 
@@ -467,7 +467,7 @@
         return NAME_NOT_FOUND;
     }
 
-    service->dump(pipeWriteFd, mArgs);
+    service->dump(pipeWriteFd.get(), mArgs);
 
     return NO_ERROR;
 }
@@ -526,7 +526,7 @@
     return src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24);
 }
 
-status_t LogSection::BlockingCall(int pipeWriteFd) const {
+status_t LogSection::BlockingCall(unique_fd& pipeWriteFd) const {
     // Open log buffer and getting logs since last retrieved time if any.
     unique_ptr<logger_list, void (*)(logger_list*)> loggers(
             gLastLogsRetrieved.find(mLogID) == gLastLogsRetrieved.end()
@@ -643,7 +643,7 @@
         }
     }
     gLastLogsRetrieved[mLogID] = lastTimestamp;
-    if (!proto.flush(pipeWriteFd) && errno == EPIPE) {
+    if (!proto.flush(pipeWriteFd.get()) && errno == EPIPE) {
         ALOGE("[%s] wrote to a broken pipe\n", this->name.string());
         return EPIPE;
     }
@@ -660,7 +660,7 @@
 
 TombstoneSection::~TombstoneSection() {}
 
-status_t TombstoneSection::BlockingCall(int pipeWriteFd) const {
+status_t TombstoneSection::BlockingCall(unique_fd& pipeWriteFd) const {
     std::unique_ptr<DIR, decltype(&closedir)> proc(opendir("/proc"), closedir);
     if (proc.get() == nullptr) {
         ALOGE("opendir /proc failed: %s\n", strerror(errno));
@@ -768,7 +768,7 @@
         dumpPipe.readFd().reset();
     }
 
-    if (!proto.flush(pipeWriteFd) && errno == EPIPE) {
+    if (!proto.flush(pipeWriteFd.get()) && errno == EPIPE) {
         ALOGE("[%s] wrote to a broken pipe\n", this->name.string());
         if (err != NO_ERROR) {
             return EPIPE;
@@ -778,6 +778,22 @@
     return err;
 }
 
+// ================================================================================
+BringYourOwnSection::BringYourOwnSection(int id, const char* customName, const uid_t callingUid,
+        const sp<IIncidentDumpCallback>& callback)
+    : WorkerThreadSection(id, REMOTE_CALL_TIMEOUT_MS), uid(callingUid), mCallback(callback) {
+    name = "registered ";
+    name += customName;
+}
+
+BringYourOwnSection::~BringYourOwnSection() {}
+
+status_t BringYourOwnSection::BlockingCall(unique_fd& pipeWriteFd) const {
+    android::os::ParcelFileDescriptor pfd(std::move(pipeWriteFd));
+    mCallback->onDumpSection(pfd);
+    return NO_ERROR;
+}
+
 }  // namespace incidentd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/incidentd/src/Section.h b/cmds/incidentd/src/Section.h
index fcf12f7..0bb9da9 100644
--- a/cmds/incidentd/src/Section.h
+++ b/cmds/incidentd/src/Section.h
@@ -23,6 +23,8 @@
 #include <stdarg.h>
 #include <map>
 
+#include <android/os/IIncidentDumpCallback.h>
+
 #include <utils/String16.h>
 #include <utils/String8.h>
 #include <utils/Vector.h>
@@ -89,7 +91,7 @@
 
     virtual status_t Execute(ReportWriter* writer) const;
 
-    virtual status_t BlockingCall(int pipeWriteFd) const = 0;
+    virtual status_t BlockingCall(unique_fd& pipeWriteFd) const = 0;
 };
 
 /**
@@ -117,7 +119,7 @@
     DumpsysSection(int id, const char* service, ...);
     virtual ~DumpsysSection();
 
-    virtual status_t BlockingCall(int pipeWriteFd) const;
+    virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
 
 private:
     String16 mService;
@@ -132,7 +134,7 @@
     SystemPropertyDumpsysSection(int id, const char* service, ...);
     virtual ~SystemPropertyDumpsysSection();
 
-    virtual status_t BlockingCall(int pipeWriteFd) const;
+    virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
 
 private:
     String16 mService;
@@ -153,7 +155,7 @@
     LogSection(int id, const char* logID, ...);
     virtual ~LogSection();
 
-    virtual status_t BlockingCall(int pipeWriteFd) const;
+    virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
 
 private:
     log_id_t mLogID;
@@ -169,12 +171,29 @@
     TombstoneSection(int id, const char* type, int64_t timeoutMs = 120000 /* 2 minutes */);
     virtual ~TombstoneSection();
 
-    virtual status_t BlockingCall(int pipeWriteFd) const;
+    virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
 
 private:
     std::string mType;
 };
 
+/**
+ * Section that gets data from a registered dump callback.
+ */
+class BringYourOwnSection : public WorkerThreadSection {
+public:
+    const uid_t uid;
+
+    BringYourOwnSection(int id, const char* customName, const uid_t callingUid,
+        const sp<IIncidentDumpCallback>& callback);
+    virtual ~BringYourOwnSection();
+
+    virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
+
+private:
+    const sp<IIncidentDumpCallback>& mCallback;
+};
+
 
 /**
  * These sections will not be generated when doing an 'all' report, either
diff --git a/cmds/sm/src/com/android/commands/sm/Sm.java b/cmds/sm/src/com/android/commands/sm/Sm.java
index 6033655..c2ee6dc 100644
--- a/cmds/sm/src/com/android/commands/sm/Sm.java
+++ b/cmds/sm/src/com/android/commands/sm/Sm.java
@@ -103,6 +103,10 @@
             runSetVirtualDisk();
         } else if ("set-isolated-storage".equals(op)) {
             runIsolatedStorage();
+        } else if ("start-checkpoint".equals(op)) {
+            runStartCheckpoint();
+        } else if ("supports-checkpoint".equals(op)) {
+            runSupportsCheckpoint();
         } else {
             throw new IllegalArgumentException();
         }
@@ -313,6 +317,27 @@
         }
     }
 
+    private void runStartCheckpoint() throws RemoteException {
+        final String numRetriesString = nextArg();
+        if (numRetriesString == null) {
+            throw new IllegalArgumentException("Expected <num-retries>");
+        }
+        int numRetries;
+        try {
+            numRetries = Integer.parseInt(numRetriesString);
+        } catch (NumberFormatException e) {
+            throw new IllegalArgumentException("<num-retries> must be a positive integer");
+        }
+        if (numRetries <= 0) {
+            throw new IllegalArgumentException("<num-retries> must be a positive integer");
+        }
+        mSm.startCheckpoint(numRetries);
+    }
+
+    private void runSupportsCheckpoint() throws RemoteException {
+        System.out.println(mSm.supportsCheckpoint());
+    }
+
     private String nextArg() {
         if (mNextArg >= mArgs.length) {
             return null;
@@ -344,6 +369,10 @@
         System.err.println("");
         System.err.println("       sm set-isolated-storage [on|off|default]");
         System.err.println("");
+        System.err.println("       sm start-checkpoint <num-retries>");
+        System.err.println("");
+        System.err.println("       sm supports-checkpoint");
+        System.err.println("");
         return 1;
     }
 }
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 1441d48..cbece78 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -338,6 +338,7 @@
         BootTimeEventElapsedTime boot_time_event_elapsed_time_reported = 240;
         BootTimeEventUtcTime boot_time_event_utc_time_reported = 241;
         BootTimeEventErrorCode boot_time_event_error_code_reported = 242;
+        UserspaceRebootReported userspace_reboot_reported = 243;
     }
 
     // Pulled events will start at field 10000.
@@ -7904,4 +7905,42 @@
     optional SamplingStrategy sampling_strategy = 6;
 }
 
-
+/*
+ * Logs userspace reboot outcome and duration.
+ *
+ * Logged from:
+ *   frameworks/base/core/java/com/android/server/BootReceiver.java
+ */
+message UserspaceRebootReported {
+    // Possible outcomes of userspace reboot.
+    enum Outcome {
+        // Default value in case platform failed to determine the outcome.
+        OUTCOME_UNKNOWN = 0;
+        // Userspace reboot succeeded (i.e. boot completed without a fall back to hard reboot).
+        SUCCESS = 1;
+        // Userspace reboot shutdown sequence was aborted.
+        FAILED_SHUTDOWN_SEQUENCE_ABORTED = 2;
+        // Remounting userdata into checkpointing mode failed.
+        FAILED_USERDATA_REMOUNT = 3;
+        // Device didn't finish booting before timeout and userspace reboot watchdog issued a hard
+        // reboot.
+        FAILED_USERSPACE_REBOOT_WATCHDOG_TRIGGERED = 4;
+    }
+    // Outcome of userspace reboot. Always set.
+    optional Outcome outcome = 1;
+    // Duration of userspace reboot in case it has a successful outcome.
+    // Duration is measured as time between userspace reboot was initiated and until boot completed
+    // (e.g. sys.boot_completed=1).
+    optional int64 duration_millis = 2;
+    // State of primary user's (user0) credential encryption storage.
+    enum UserEncryptionState {
+        // Default value.
+        USER_ENCRYPTION_STATE_UNKNOWN = 0;
+        // Credential encrypted storage is unlocked.
+        UNLOCKED = 1;
+        // Credential encrypted storage is locked.
+        LOCKED = 2;
+    }
+    // State of primary user's encryption storage at the moment boot completed. Always set.
+    optional UserEncryptionState user_encryption_state = 3;
+}
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 731afe8..b5920cb 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -60,10 +60,6 @@
 
 std::map<PullerKey, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = {
 
-        // kernel_wakelock
-        {{.atomTag = android::util::KERNEL_WAKELOCK},
-         {.puller = new StatsCompanionServicePuller(android::util::KERNEL_WAKELOCK)}},
-
         // subsystem_sleep_state
         {{.atomTag = android::util::SUBSYSTEM_SLEEP_STATE},
          {.puller = new SubsystemSleepStatePuller()}},
@@ -72,37 +68,6 @@
         {{.atomTag = android::util::ON_DEVICE_POWER_MEASUREMENT},
          {.puller = new PowerStatsPuller()}},
 
-        // cpu_time_per_freq
-        {{.atomTag = android::util::CPU_TIME_PER_FREQ},
-         {.additiveFields = {3},
-          .puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_FREQ)}},
-
-        // cpu_time_per_uid
-        {{.atomTag = android::util::CPU_TIME_PER_UID},
-         {.additiveFields = {2, 3},
-          .puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_UID)}},
-
-        // cpu_time_per_uid_freq
-        // the throttling is 3sec, handled in
-        // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
-        {{.atomTag = android::util::CPU_TIME_PER_UID_FREQ},
-         {.additiveFields = {4},
-          .puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_UID_FREQ)}},
-
-        // cpu_active_time
-        // the throttling is 3sec, handled in
-        // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
-        {{.atomTag = android::util::CPU_ACTIVE_TIME},
-         {.additiveFields = {2},
-          .puller = new StatsCompanionServicePuller(android::util::CPU_ACTIVE_TIME)}},
-
-        // cpu_cluster_time
-        // the throttling is 3sec, handled in
-        // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
-        {{.atomTag = android::util::CPU_CLUSTER_TIME},
-         {.additiveFields = {3},
-          .puller = new StatsCompanionServicePuller(android::util::CPU_CLUSTER_TIME)}},
-
         // wifi_activity_energy_info
         {{.atomTag = android::util::WIFI_ACTIVITY_INFO},
          {.puller = new StatsCompanionServicePuller(android::util::WIFI_ACTIVITY_INFO)}},
@@ -118,10 +83,6 @@
           .pullTimeoutNs = NS_PER_SEC / 2,
          }},
 
-        // system_uptime
-        {{.atomTag = android::util::SYSTEM_UPTIME},
-         {.puller = new StatsCompanionServicePuller(android::util::SYSTEM_UPTIME)}},
-
         // remaining_battery_capacity
         {{.atomTag = android::util::REMAINING_BATTERY_CAPACITY},
          {.puller = new ResourceHealthManagerPuller(android::util::REMAINING_BATTERY_CAPACITY)}},
@@ -256,10 +217,6 @@
          {.additiveFields = {1, 2, 3, 4},
           .puller = new StatsCompanionServicePuller(android::util::DEBUG_FAILING_ELAPSED_CLOCK)}},
 
-        // BuildInformation.
-        {{.atomTag = android::util::BUILD_INFORMATION},
-         {.puller = new StatsCompanionServicePuller(android::util::BUILD_INFORMATION)}},
-
         // RoleHolder.
         {{.atomTag = android::util::ROLE_HOLDER},
          {.puller = new StatsCompanionServicePuller(android::util::ROLE_HOLDER)}},
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index 7b651df..e0aecce 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -617,19 +617,6 @@
 
 void ValidateAttributionUidDimension(const DimensionsValue& value, int atomId, int uid) {
     EXPECT_EQ(value.field(), atomId);
-    // Attribution field.
-    EXPECT_EQ(value.value_tuple().dimensions_value(0).field(), 1);
-    // Uid only.
-    EXPECT_EQ(value.value_tuple().dimensions_value(0)
-        .value_tuple().dimensions_value_size(), 1);
-    EXPECT_EQ(value.value_tuple().dimensions_value(0)
-        .value_tuple().dimensions_value(0).field(), 1);
-    EXPECT_EQ(value.value_tuple().dimensions_value(0)
-        .value_tuple().dimensions_value(0).value_int(), uid);
-}
-
-void ValidateUidDimension(const DimensionsValue& value, int atomId, int uid) {
-    EXPECT_EQ(value.field(), atomId);
     EXPECT_EQ(value.value_tuple().dimensions_value_size(), 1);
     // Attribution field.
     EXPECT_EQ(value.value_tuple().dimensions_value(0).field(), 1);
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index e46840c..0bd8ce6 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -17,6 +17,7 @@
 package android.accessibilityservice;
 
 import android.accessibilityservice.GestureDescription.MotionEventGenerator;
+import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -26,12 +27,15 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ParceledListSlice;
+import android.graphics.Bitmap;
 import android.graphics.Region;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -48,10 +52,13 @@
 
 import com.android.internal.os.HandlerCaller;
 import com.android.internal.os.SomeArgs;
+import com.android.internal.util.Preconditions;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 
 /**
  * Accessibility services should only be used to assist users with disabilities in using
@@ -483,6 +490,9 @@
 
     private FingerprintGestureController mFingerprintGestureController;
 
+    /** @hide */
+    public static final String KEY_ACCESSIBILITY_SCREENSHOT = "screenshot";
+
     /**
      * Callback for {@link android.view.accessibility.AccessibilityEvent}s.
      *
@@ -1761,6 +1771,51 @@
     }
 
     /**
+     * Takes a screenshot of the specified display and returns it by {@link Bitmap.Config#HARDWARE}
+     * format.
+     * <p>
+     * <strong>Note:</strong> In order to take screenshot your service has
+     * to declare the capability to take screenshot by setting the
+     * {@link android.R.styleable#AccessibilityService_canTakeScreenshot}
+     * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}.
+     * Besides, This API is only supported for default display now
+     * {@link Display#DEFAULT_DISPLAY}.
+     * </p>
+     *
+     * @param displayId The logic display id, must be {@link Display#DEFAULT_DISPLAY} for
+     *                  default display.
+     * @param executor Executor on which to run the callback.
+     * @param callback The callback invoked when the taking screenshot is done.
+     *
+     * @return {@code true} if the taking screenshot accepted, {@code false} if not.
+     */
+    public boolean takeScreenshot(int displayId, @NonNull @CallbackExecutor Executor executor,
+            @NonNull Consumer<Bitmap> callback) {
+        Preconditions.checkNotNull(executor, "executor cannot be null");
+        Preconditions.checkNotNull(callback, "callback cannot be null");
+        final IAccessibilityServiceConnection connection =
+                AccessibilityInteractionClient.getInstance().getConnection(
+                        mConnectionId);
+        if (connection == null) {
+            return false;
+        }
+        try {
+            connection.takeScreenshotWithCallback(displayId, new RemoteCallback((result) -> {
+                final Bitmap screenshot = result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT);
+                final long identity = Binder.clearCallingIdentity();
+                try {
+                    executor.execute(() -> callback.accept(screenshot));
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
+            }));
+        } catch (RemoteException re) {
+            throw new RuntimeException(re);
+        }
+        return true;
+    }
+
+    /**
      * Implement to return the implementation of the internal accessibility
      * service interface.
      */
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 5e2c1fa..12f2c3b 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -86,6 +86,7 @@
  * @attr ref android.R.styleable#AccessibilityService_settingsActivity
  * @attr ref android.R.styleable#AccessibilityService_nonInteractiveUiTimeout
  * @attr ref android.R.styleable#AccessibilityService_interactiveUiTimeout
+ * @attr ref android.R.styleable#AccessibilityService_canTakeScreenshot
  * @see AccessibilityService
  * @see android.view.accessibility.AccessibilityEvent
  * @see android.view.accessibility.AccessibilityManager
@@ -136,6 +137,12 @@
      */
     public static final int CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES = 0x00000040;
 
+    /**
+     * Capability: This accessibility service can take screenshot.
+     * @see android.R.styleable#AccessibilityService_canTakeScreenshot
+     */
+    public static final int CAPABILITY_CAN_TAKE_SCREENSHOT = 0x00000080;
+
     private static SparseArray<CapabilityInfo> sAvailableCapabilityInfos;
 
     /**
@@ -625,6 +632,10 @@
                     .AccessibilityService_canRequestFingerprintGestures, false)) {
                 mCapabilities |= CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES;
             }
+            if (asAttributes.getBoolean(com.android.internal.R.styleable
+                    .AccessibilityService_canTakeScreenshot, false)) {
+                mCapabilities |= CAPABILITY_CAN_TAKE_SCREENSHOT;
+            }
             TypedValue peekedValue = asAttributes.peekValue(
                     com.android.internal.R.styleable.AccessibilityService_description);
             if (peekedValue != null) {
@@ -794,6 +805,7 @@
      * @see #CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS
      * @see #CAPABILITY_CAN_CONTROL_MAGNIFICATION
      * @see #CAPABILITY_CAN_PERFORM_GESTURES
+     * @see #CAPABILITY_CAN_TAKE_SCREENSHOT
      */
     public int getCapabilities() {
         return mCapabilities;
@@ -810,6 +822,7 @@
      * @see #CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS
      * @see #CAPABILITY_CAN_CONTROL_MAGNIFICATION
      * @see #CAPABILITY_CAN_PERFORM_GESTURES
+     * @see #CAPABILITY_CAN_TAKE_SCREENSHOT
      *
      * @hide
      */
@@ -1253,6 +1266,8 @@
                 return "CAPABILITY_CAN_PERFORM_GESTURES";
             case CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES:
                 return "CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES";
+            case CAPABILITY_CAN_TAKE_SCREENSHOT:
+                return "CAPABILITY_CAN_TAKE_SCREENSHOT";
             default:
                 return "UNKNOWN";
         }
@@ -1314,6 +1329,10 @@
                     new CapabilityInfo(CAPABILITY_CAN_PERFORM_GESTURES,
                             R.string.capability_title_canPerformGestures,
                             R.string.capability_desc_canPerformGestures));
+            sAvailableCapabilityInfos.put(CAPABILITY_CAN_TAKE_SCREENSHOT,
+                    new CapabilityInfo(CAPABILITY_CAN_TAKE_SCREENSHOT,
+                            R.string.capability_title_canTakeScreenshot,
+                            R.string.capability_desc_canTakeScreenshot));
             if ((context == null) || fingerprintAvailable(context)) {
                 sAvailableCapabilityInfos.put(CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES,
                         new CapabilityInfo(CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES,
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 656f87f..4ea5c62 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -18,8 +18,10 @@
 
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.content.pm.ParceledListSlice;
+import android.graphics.Bitmap;
 import android.graphics.Region;
 import android.os.Bundle;
+import android.os.RemoteCallback;
 import android.view.MagnificationSpec;
 import android.view.MotionEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
@@ -102,4 +104,10 @@
     boolean isFingerprintGestureDetectionAvailable();
 
     IBinder getOverlayWindowToken(int displayid);
+
+    int getWindowIdForLeashToken(IBinder token);
+
+    Bitmap takeScreenshot(int displayId);
+
+    void takeScreenshotWithCallback(int displayId, in RemoteCallback callback);
 }
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 1c6a561..d39a8c4 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -133,6 +133,10 @@
     // Default flags to use with PackageManager when no flags are given.
     private final static int sDefaultFlags = PackageManager.GET_SHARED_LIBRARY_FILES;
 
+    // Name of the resource which provides background permission button string
+    public static final String APP_PERMISSION_BUTTON_ALLOW_ALWAYS =
+            "app_permission_button_allow_always";
+
     private final Object mLock = new Object();
 
     @GuardedBy("mLock")
@@ -807,6 +811,26 @@
     }
 
     @Override
+    public CharSequence getBackgroundPermissionButtonLabel() {
+        try {
+
+            String permissionController = getPermissionControllerPackageName();
+            Context context =
+                    mContext.createPackageContext(permissionController, 0);
+
+            int textId = context.getResources().getIdentifier(APP_PERMISSION_BUTTON_ALLOW_ALWAYS,
+                    "string", "com.android.permissioncontroller");
+//                    permissionController); STOPSHIP b/147434671
+            if (textId != 0) {
+                return context.getText(textId);
+            }
+        } catch (NameNotFoundException e) {
+            Log.e(TAG, "Permission controller not found.", e);
+        }
+        return "";
+    }
+
+    @Override
     public int checkSignatures(String pkg1, String pkg2) {
         try {
             return mPM.checkSignatures(pkg1, pkg2);
diff --git a/core/java/android/app/DisabledWallpaperManager.java b/core/java/android/app/DisabledWallpaperManager.java
index 5185941..1f323c3 100644
--- a/core/java/android/app/DisabledWallpaperManager.java
+++ b/core/java/android/app/DisabledWallpaperManager.java
@@ -41,8 +41,7 @@
     // Don't need to worry about synchronization
     private static DisabledWallpaperManager sInstance;
 
-    // TODO(b/138939803): STOPSHIP changed to false and/or remove it
-    private static final boolean DEBUG = true;
+    private static final boolean DEBUG = false;
 
     @NonNull
     static DisabledWallpaperManager getInstance() {
@@ -53,7 +52,6 @@
     }
 
     private DisabledWallpaperManager() {
-        super(null, null, null);
     }
 
     @Override
@@ -66,10 +64,6 @@
         return false;
     }
 
-    // TODO(b/138939803): STOPSHIP methods below should not be necessary,
-    // callers should check if isWallpaperSupported(), consider removing them to keep this class
-    // simpler
-
     private static <T> T unsupported() {
         if (DEBUG) Log.w(TAG, "unsupported method called; returning null", new Exception());
         return null;
@@ -343,4 +337,9 @@
     public boolean isWallpaperBackupEligible(int which) {
         return unsupportedBoolean();
     }
+
+    @Override
+    public boolean wallpaperSupportsWcg(int which) {
+        return unsupportedBoolean();
+    }
 }
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index bd6baec..580382e 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -355,6 +355,12 @@
             in Bundle options, int userId);
     @UnsupportedAppUsage
     int stopUser(int userid, boolean force, in IStopUserCallback callback);
+    /**
+     * Check {@link com.android.server.am.ActivityManagerService#stopUserWithDelayedLocking(int, boolean, IStopUserCallback)}
+     * for details.
+     */
+    int stopUserWithDelayedLocking(int userid, boolean force, in IStopUserCallback callback);
+
     @UnsupportedAppUsage
     void registerUserSwitchObserver(in IUserSwitchObserver observer, in String name);
     void unregisterUserSwitchObserver(in IUserSwitchObserver observer);
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index fcdb7cc..7af7a4a 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -94,7 +94,7 @@
     void updateNotificationChannelGroupForPackage(String pkg, int uid, in NotificationChannelGroup group);
     void updateNotificationChannelForPackage(String pkg, int uid, in NotificationChannel channel);
     NotificationChannel getNotificationChannel(String callingPkg, int userId, String pkg, String channelId);
-    NotificationChannel getConversationNotificationChannel(String callingPkg, int userId, String pkg, String channelId, String conversationId);
+    NotificationChannel getConversationNotificationChannel(String callingPkg, int userId, String pkg, String channelId, boolean returnParentIfNoConversationChannel, String conversationId);
     void createConversationNotificationChannelForPackage(String pkg, int uid, in NotificationChannel parentChannel, String conversationId);
     NotificationChannel getNotificationChannelForPackage(String pkg, int uid, String channelId, boolean includeDeleted);
     void deleteNotificationChannel(String pkg, String channelId);
diff --git a/core/java/android/app/IUiAutomationConnection.aidl b/core/java/android/app/IUiAutomationConnection.aidl
index 8c3180b..80ba464 100644
--- a/core/java/android/app/IUiAutomationConnection.aidl
+++ b/core/java/android/app/IUiAutomationConnection.aidl
@@ -39,7 +39,6 @@
     boolean injectInputEvent(in InputEvent event, boolean sync);
     void syncInputTransactions();
     boolean setRotation(int rotation);
-    Bitmap takeScreenshot(in Rect crop, int rotation);
     boolean clearWindowContentFrameStats(int windowId);
     WindowContentFrameStats getWindowContentFrameStats(int windowId);
     void clearWindowAnimationFrameStats();
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 775b1d1..70262b0 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -251,7 +251,7 @@
     }
 
     private AppComponentFactory createAppFactory(ApplicationInfo appInfo, ClassLoader cl) {
-        if (appInfo.appComponentFactory != null && cl != null) {
+        if (mIncludeCode && appInfo.appComponentFactory != null && cl != null) {
             try {
                 return (AppComponentFactory)
                         cl.loadClass(appInfo.appComponentFactory).newInstance();
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index a33c2c1..bdc7b99 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -31,6 +31,7 @@
 import android.provider.Settings;
 import android.service.notification.NotificationListenerService;
 import android.text.TextUtils;
+import android.util.Log;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.util.Preconditions;
@@ -71,7 +72,7 @@
      * Conversation id to use for apps that aren't providing them yet.
      * @hide
      */
-    public static final String PLACEHOLDER_CONVERSATION_ID = "placeholder_id";
+    public static final String PLACEHOLDER_CONVERSATION_ID = ":placeholder_id";
 
     /**
      * The maximum length for text fields in a NotificationChannel. Fields will be truncated at this
@@ -826,8 +827,8 @@
         setBlockableSystem(safeBool(parser, ATT_BLOCKABLE_SYSTEM, false));
         setAllowBubbles(safeBool(parser, ATT_ALLOW_BUBBLE, DEFAULT_ALLOW_BUBBLE));
         setOriginalImportance(safeInt(parser, ATT_ORIG_IMP, DEFAULT_IMPORTANCE));
-        setConversationId(parser.getAttributeValue(ATT_PARENT_CHANNEL, null),
-                parser.getAttributeValue(ATT_CONVERSATION_ID, null));
+        setConversationId(parser.getAttributeValue(null, ATT_PARENT_CHANNEL),
+                parser.getAttributeValue(null, ATT_CONVERSATION_ID));
     }
 
     @Nullable
@@ -1175,7 +1176,7 @@
                 + ", mImportanceLockedDefaultApp=" + mImportanceLockedDefaultApp
                 + ", mOriginalImp=" + mOriginalImportance
                 + ", mParent=" + mParentId
-                + ", mConversationId" + mConversationId;
+                + ", mConversationId=" + mConversationId;
     }
 
     /** @hide */
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 61c1098..1a8e15c 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -855,7 +855,8 @@
         INotificationManager service = getService();
         try {
             return service.getConversationNotificationChannel(mContext.getOpPackageName(),
-                    mContext.getUserId(), mContext.getPackageName(), channelId, conversationId);
+                    mContext.getUserId(), mContext.getPackageName(), channelId, true,
+                    conversationId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index a9be9ea..7b21f12 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -54,6 +54,7 @@
 import android.content.integrity.IAppIntegrityManager;
 import android.content.om.IOverlayManager;
 import android.content.om.OverlayManager;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.CrossProfileApps;
 import android.content.pm.DataLoaderManager;
 import android.content.pm.ICrossProfileApps;
@@ -693,20 +694,20 @@
                     throws ServiceNotFoundException {
                 final IBinder b = ServiceManager.getService(Context.WALLPAPER_SERVICE);
                 if (b == null) {
-                    // There are 2 reason service can be null:
-                    // 1.Device doesn't support it - that's fine
-                    // 2.App is running on instant mode - should fail
-                    final boolean enabled = Resources.getSystem()
-                            .getBoolean(com.android.internal.R.bool.config_enableWallpaperService);
-                    if (!enabled) {
-                        // Life moves on...
-                        return DisabledWallpaperManager.getInstance();
-                    }
-                    if (ctx.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.P) {
+                    ApplicationInfo appInfo = ctx.getApplicationInfo();
+                    if (appInfo.targetSdkVersion >= Build.VERSION_CODES.P
+                            && appInfo.isInstantApp()) {
                         // Instant app
                         throw new ServiceNotFoundException(Context.WALLPAPER_SERVICE);
                     }
+                    final boolean enabled = Resources.getSystem()
+                            .getBoolean(com.android.internal.R.bool.config_enableWallpaperService);
+                    if (!enabled) {
+                        // Device doesn't support wallpaper, return a limited manager
+                        return DisabledWallpaperManager.getInstance();
+                    }
                     // Bad state - WallpaperManager methods will throw exception
+                    Log.e(TAG, "No wallpaper service");
                 }
                 IWallpaperManager service = IWallpaperManager.Stub.asInterface(b);
                 return new WallpaperManager(service, ctx.getOuterContext(),
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 18a3e6e..2579bd1 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -27,10 +27,7 @@
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.Bitmap;
-import android.graphics.Point;
-import android.graphics.Rect;
 import android.graphics.Region;
-import android.hardware.display.DisplayManagerGlobal;
 import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -831,39 +828,20 @@
     }
 
     /**
-     * Takes a screenshot.
+     * Takes a screenshot of the default display and returns it by {@link Bitmap.Config#HARDWARE}
+     * format.
      *
      * @return The screenshot bitmap on success, null otherwise.
      */
     public Bitmap takeScreenshot() {
+        final int connectionId;
         synchronized (mLock) {
             throwIfNotConnectedLocked();
+            connectionId = mConnectionId;
         }
-        Display display = DisplayManagerGlobal.getInstance()
-                .getRealDisplay(Display.DEFAULT_DISPLAY);
-        Point displaySize = new Point();
-        display.getRealSize(displaySize);
-
-        int rotation = display.getRotation();
-
-        // Take the screenshot
-        Bitmap screenShot = null;
-        try {
-            // Calling out without a lock held.
-            screenShot = mUiAutomationConnection.takeScreenshot(
-                    new Rect(0, 0, displaySize.x, displaySize.y), rotation);
-            if (screenShot == null) {
-                return null;
-            }
-        } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error while taking screnshot!", re);
-            return null;
-        }
-
-        // Optimization
-        screenShot.setHasAlpha(false);
-
-        return screenShot;
+        // Calling out without a lock held.
+        return AccessibilityInteractionClient.getInstance()
+                .takeScreenshot(connectionId, Display.DEFAULT_DISPLAY);
     }
 
     /**
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 82e9881..4fb9743 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -21,8 +21,6 @@
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Rect;
 import android.hardware.input.InputManager;
 import android.os.Binder;
 import android.os.IBinder;
@@ -180,23 +178,6 @@
     }
 
     @Override
-    public Bitmap takeScreenshot(Rect crop, int rotation) {
-        synchronized (mLock) {
-            throwIfCalledByNotTrustedUidLocked();
-            throwIfShutdownLocked();
-            throwIfNotConnectedLocked();
-        }
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            int width = crop.width();
-            int height = crop.height();
-            return SurfaceControl.screenshot(crop, width, height, rotation);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    @Override
     public boolean clearWindowContentFrameStats(int windowId) throws RemoteException {
         synchronized (mLock) {
             throwIfCalledByNotTrustedUidLocked();
@@ -449,7 +430,8 @@
         info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
                 | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
                 | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY
-                | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS);
+                | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS
+                | AccessibilityServiceInfo.CAPABILITY_CAN_TAKE_SCREENSHOT);
         try {
             // Calling out with a lock held is fine since if the system
             // process is gone the client calling in will be killed.
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 2507991..9ad3d38 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -543,6 +543,13 @@
         mCmProxy = new ColorManagementProxy(context);
     }
 
+    // no-op constructor called just by DisabledWallpaperManager
+    /*package*/ WallpaperManager() {
+        mContext = null;
+        mCmProxy = null;
+        mWcgEnabled = false;
+    }
+
     /**
      * Retrieve a WallpaperManager associated with the given Context.
      */
@@ -568,8 +575,10 @@
      *
      * @see Configuration#isScreenWideColorGamut()
      * @return True if wcg should be enabled for this device.
+     * @hide
      */
-    private boolean shouldEnableWideColorGamut() {
+    @TestApi
+    public boolean shouldEnableWideColorGamut() {
         return mWcgEnabled;
     }
 
@@ -877,6 +886,7 @@
      * @see #FLAG_SYSTEM
      * @hide
      */
+    @TestApi
     @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
     public boolean wallpaperSupportsWcg(int which) {
         if (!shouldEnableWideColorGamut()) {
@@ -893,6 +903,8 @@
      *
      * @hide
      */
+    @TestApi
+    @Nullable
     @UnsupportedAppUsage
     public Bitmap getBitmap() {
         return getBitmap(false);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index caaa686..c584575 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -7418,7 +7418,9 @@
      * @param userHandle The user for whom to check the caller-id permission
      * @hide
      */
-    public boolean getBluetoothContactSharingDisabled(UserHandle userHandle) {
+    @SystemApi
+    @RequiresPermission(permission.INTERACT_ACROSS_USERS)
+    public boolean getBluetoothContactSharingDisabled(@NonNull UserHandle userHandle) {
         if (mService != null) {
             try {
                 return mService.getBluetoothContactSharingDisabledForUser(userHandle
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java
index c8f2ff3..567eb4a 100644
--- a/core/java/android/app/backup/BackupTransport.java
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -85,6 +85,15 @@
     public static final int FLAG_NON_INCREMENTAL = 1 << 2;
 
     /**
+     * For key value backup, indicates that the backup contains no new data since the last backup
+     * attempt completed without any errors. The transport should use this to record that
+     * a successful backup attempt has been completed but no backup data has been changed.
+     *
+     * @see #performBackup(PackageInfo, ParcelFileDescriptor, int)
+     */
+    public static final int FLAG_DATA_NOT_CHANGED = 1 << 3;
+
+    /**
      * Used as a boolean extra in the binding intent of transports. We pass {@code true} to
      * notify transports that the current connection is used for registering the transport.
      */
@@ -302,7 +311,8 @@
      *   BackupService.doBackup() method.  This may be a pipe rather than a file on
      *   persistent media, so it may not be seekable.
      * @param flags a combination of {@link BackupTransport#FLAG_USER_INITIATED}, {@link
-     *   BackupTransport#FLAG_NON_INCREMENTAL}, {@link BackupTransport#FLAG_INCREMENTAL}, or 0.
+     *   BackupTransport#FLAG_NON_INCREMENTAL}, {@link BackupTransport#FLAG_INCREMENTAL},
+     *   {@link BackupTransport#FLAG_DATA_NOT_CHANGED},or 0.
      * @return one of {@link BackupTransport#TRANSPORT_OK} (OK so far),
      *  {@link BackupTransport#TRANSPORT_PACKAGE_REJECTED} (to suppress backup of this
      *  specific package, but allow others to proceed),
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index d840c1c..6ab880d 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -451,21 +451,7 @@
 
         /** @hide */
         public Event(Event orig) {
-            mPackage = orig.mPackage;
-            mClass = orig.mClass;
-            mInstanceId = orig.mInstanceId;
-            mTaskRootPackage = orig.mTaskRootPackage;
-            mTaskRootClass = orig.mTaskRootClass;
-            mTimeStamp = orig.mTimeStamp;
-            mEventType = orig.mEventType;
-            mConfiguration = orig.mConfiguration;
-            mShortcutId = orig.mShortcutId;
-            mAction = orig.mAction;
-            mContentType = orig.mContentType;
-            mContentAnnotations = orig.mContentAnnotations;
-            mFlags = orig.mFlags;
-            mBucketAndReason = orig.mBucketAndReason;
-            mNotificationChannelId = orig.mNotificationChannelId;
+            copyFrom(orig);
         }
 
         /**
@@ -622,6 +608,24 @@
             // which instant apps can't use anyway, so there's no need to hide them.
             return ret;
         }
+
+        private void copyFrom(Event orig) {
+            mPackage = orig.mPackage;
+            mClass = orig.mClass;
+            mInstanceId = orig.mInstanceId;
+            mTaskRootPackage = orig.mTaskRootPackage;
+            mTaskRootClass = orig.mTaskRootClass;
+            mTimeStamp = orig.mTimeStamp;
+            mEventType = orig.mEventType;
+            mConfiguration = orig.mConfiguration;
+            mShortcutId = orig.mShortcutId;
+            mAction = orig.mAction;
+            mContentType = orig.mContentType;
+            mContentAnnotations = orig.mContentAnnotations;
+            mFlags = orig.mFlags;
+            mBucketAndReason = orig.mBucketAndReason;
+            mNotificationChannelId = orig.mNotificationChannelId;
+        }
     }
 
     // Only used when creating the resulting events. Not used for reading/unparceling.
@@ -725,10 +729,14 @@
             return false;
         }
 
-        readEventFromParcel(mParcel, eventOut);
+        if (mParcel != null) {
+            readEventFromParcel(mParcel, eventOut);
+        } else {
+            eventOut.copyFrom(mEventsToWrite.get(mIndex));
+        }
 
         mIndex++;
-        if (mIndex >= mEventCount) {
+        if (mIndex >= mEventCount && mParcel != null) {
             mParcel.recycle();
             mParcel = null;
         }
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 9ef9574..3860508 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1825,7 +1825,7 @@
      * @throws ActivityNotFoundException &nbsp;
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+    @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
     @SystemApi
     public void startActivityAsUser(@RequiresPermission @NonNull Intent intent,
             @NonNull UserHandle user) {
@@ -1873,7 +1873,7 @@
      * @throws ActivityNotFoundException &nbsp;
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+    @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
     @UnsupportedAppUsage
     public void startActivityAsUser(@RequiresPermission Intent intent, @Nullable Bundle options,
             UserHandle userId) {
@@ -1979,7 +1979,7 @@
      * @see #startActivities(Intent[])
      * @see PackageManager#resolveActivity
      */
-    @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+    @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
     public int startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) {
         throw new RuntimeException("Not implemented. Must override in a subclass.");
     }
diff --git a/core/java/android/content/pm/InstallSourceInfo.java b/core/java/android/content/pm/InstallSourceInfo.java
index 4d235f1..c0fdcc9 100644
--- a/core/java/android/content/pm/InstallSourceInfo.java
+++ b/core/java/android/content/pm/InstallSourceInfo.java
@@ -29,32 +29,39 @@
 
     @Nullable private final String mInitiatingPackageName;
 
+    @Nullable private final SigningInfo mInitiatingPackageSigningInfo;
+
     @Nullable private final String mOriginatingPackageName;
 
     @Nullable private final String mInstallingPackageName;
 
     /** @hide */
     public InstallSourceInfo(@Nullable String initiatingPackageName,
+            @Nullable SigningInfo initiatingPackageSigningInfo,
             @Nullable String originatingPackageName, @Nullable String installingPackageName) {
-        this.mInitiatingPackageName = initiatingPackageName;
-        this.mOriginatingPackageName = originatingPackageName;
-        this.mInstallingPackageName = installingPackageName;
+        mInitiatingPackageName = initiatingPackageName;
+        mInitiatingPackageSigningInfo = initiatingPackageSigningInfo;
+        mOriginatingPackageName = originatingPackageName;
+        mInstallingPackageName = installingPackageName;
     }
 
     @Override
     public int describeContents() {
-        return 0;
+        return mInitiatingPackageSigningInfo == null
+                ? 0 : mInitiatingPackageSigningInfo.describeContents();
     }
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeString(mInitiatingPackageName);
+        dest.writeParcelable(mInitiatingPackageSigningInfo, flags);
         dest.writeString(mOriginatingPackageName);
         dest.writeString(mInstallingPackageName);
     }
 
     private InstallSourceInfo(Parcel source) {
         mInitiatingPackageName = source.readString();
+        mInitiatingPackageSigningInfo = source.readParcelable(SigningInfo.class.getClassLoader());
         mOriginatingPackageName = source.readString();
         mInstallingPackageName = source.readString();
     }
@@ -66,6 +73,14 @@
     }
 
     /**
+     * Information about the signing certificates used to sign the initiating package, if available.
+     */
+    @Nullable
+    public SigningInfo getInitiatingPackageSigningInfo() {
+        return mInitiatingPackageSigningInfo;
+    }
+
+    /**
      * The name of the package on behalf of which the initiating package requested the installation,
      * or null if not available.
      * <p>
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 4bfc40e..5ca50c2 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -4389,6 +4389,18 @@
     public abstract boolean shouldShowRequestPermissionRationale(@NonNull String permName);
 
     /**
+     * Gets the string that is displayed on the button which corresponds to granting background
+     * location in settings. The intended use for this is to help apps instruct users how to
+     * grant a background permission by providing the string that users will see.
+     *
+     * @return the string shown on the button for granting background location
+     */
+    @NonNull
+    public CharSequence getBackgroundPermissionButtonLabel() {
+        return "";
+    }
+
+    /**
      * Returns an {@link android.content.Intent} suitable for passing to
      * {@link android.app.Activity#startActivityForResult(android.content.Intent, int)}
      * which prompts the user to grant permissions to this application.
@@ -6053,6 +6065,11 @@
      * If the calling application does not hold the INSTALL_PACKAGES permission then
      * the result will always return {@code null} from
      * {@link InstallSourceInfo#getOriginatingPackageName()}.
+     * <p>
+     * If the package that requested the install has been uninstalled, then information about it
+     * will only be returned from {@link InstallSourceInfo#getInitiatingPackageName()} and
+     * {@link InstallSourceInfo#getInitiatingPackageSigningInfo()} if the calling package is
+     * requesting its own install information and is not an instant app.
      *
      * @param packageName The name of the package to query
      * @throws NameNotFoundException if the given package name is not installed
diff --git a/core/java/android/hardware/biometrics/BiometricAuthenticator.java b/core/java/android/hardware/biometrics/BiometricAuthenticator.java
index 698876b..11cf2d6 100644
--- a/core/java/android/hardware/biometrics/BiometricAuthenticator.java
+++ b/core/java/android/hardware/biometrics/BiometricAuthenticator.java
@@ -18,6 +18,7 @@
 
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
+import android.hardware.biometrics.BiometricPrompt.AuthenticationResultType;
 import android.os.CancellationSignal;
 import android.os.Parcelable;
 
@@ -119,6 +120,7 @@
     class AuthenticationResult {
         private Identifier mIdentifier;
         private CryptoObject mCryptoObject;
+        private @AuthenticationResultType int mAuthenticationType;
         private int mUserId;
 
         /**
@@ -129,27 +131,41 @@
         /**
          * Authentication result
          * @param crypto
+         * @param authenticationType
          * @param identifier
          * @param userId
          * @hide
          */
-        public AuthenticationResult(CryptoObject crypto, Identifier identifier,
+        public AuthenticationResult(CryptoObject crypto,
+                @AuthenticationResultType int authenticationType, Identifier identifier,
                 int userId) {
             mCryptoObject = crypto;
+            mAuthenticationType = authenticationType;
             mIdentifier = identifier;
             mUserId = userId;
         }
 
         /**
-         * Obtain the crypto object associated with this transaction
-         * @return crypto object provided to {@link BiometricAuthenticator#authenticate(
-         * CryptoObject, CancellationSignal, Executor, AuthenticationCallback)}
+         * Provides the crypto object associated with this transaction.
+         * @return The crypto object provided to {@link BiometricPrompt#authenticate(
+         * BiometricPrompt.CryptoObject, CancellationSignal, Executor,
+         * BiometricPrompt.AuthenticationCallback)}
          */
         public CryptoObject getCryptoObject() {
             return mCryptoObject;
         }
 
         /**
+         * Provides the type of authentication (e.g. device credential or biometric) that was
+         * requested from and successfully provided by the user.
+         *
+         * @return An integer value representing the authentication method used.
+         */
+        public @AuthenticationResultType int getAuthenticationType() {
+            return mAuthenticationType;
+        }
+
+        /**
          * Obtain the biometric identifier associated with this operation. Applications are strongly
          * discouraged from associating specific identifiers with specific applications or
          * operations.
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index cb8fc8b..a695ce8 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -21,6 +21,7 @@
 import static android.hardware.biometrics.BiometricManager.Authenticators;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -40,6 +41,8 @@
 
 import com.android.internal.R;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.security.Signature;
 import java.util.concurrent.Executor;
 
@@ -397,9 +400,11 @@
             new IBiometricServiceReceiver.Stub() {
 
         @Override
-        public void onAuthenticationSucceeded() throws RemoteException {
+        public void onAuthenticationSucceeded(@AuthenticationResultType int authenticationType)
+                throws RemoteException {
             mExecutor.execute(() -> {
-                final AuthenticationResult result = new AuthenticationResult(mCryptoObject);
+                final AuthenticationResult result =
+                        new AuthenticationResult(mCryptoObject, authenticationType);
                 mAuthenticationCallback.onAuthenticationSucceeded(result);
             });
         }
@@ -576,28 +581,62 @@
     }
 
     /**
-     * Container for callback data from {@link #authenticate( CancellationSignal, Executor,
+     * Authentication type reported by {@link AuthenticationResult} when the user authenticated by
+     * entering their device PIN, pattern, or password.
+     */
+    public static final int AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL = 1;
+
+    /**
+     * Authentication type reported by {@link AuthenticationResult} when the user authenticated by
+     * presenting some form of biometric (e.g. fingerprint or face).
+     */
+    public static final int AUTHENTICATION_RESULT_TYPE_BIOMETRIC = 2;
+
+    /**
+     * An {@link IntDef} representing the type of auth, as reported by {@link AuthenticationResult}.
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL, AUTHENTICATION_RESULT_TYPE_BIOMETRIC})
+    public @interface AuthenticationResultType {
+    }
+
+    /**
+     * Container for callback data from {@link #authenticate(CancellationSignal, Executor,
      * AuthenticationCallback)} and {@link #authenticate(CryptoObject, CancellationSignal, Executor,
-     * AuthenticationCallback)}
+     * AuthenticationCallback)}.
      */
     public static class AuthenticationResult extends BiometricAuthenticator.AuthenticationResult {
         /**
          * Authentication result
          * @param crypto
+         * @param authenticationType
          * @hide
          */
-        public AuthenticationResult(CryptoObject crypto) {
+        public AuthenticationResult(CryptoObject crypto,
+                @AuthenticationResultType int authenticationType) {
             // Identifier and userId is not used for BiometricPrompt.
-            super(crypto, null /* identifier */, 0 /* userId */);
+            super(crypto, authenticationType, null /* identifier */, 0 /* userId */);
         }
+
         /**
-         * Obtain the crypto object associated with this transaction
-         * @return crypto object provided to {@link #authenticate( CryptoObject, CancellationSignal,
-         * Executor, AuthenticationCallback)}
+         * Provides the crypto object associated with this transaction.
+         * @return The crypto object provided to {@link #authenticate(CryptoObject,
+         * CancellationSignal, Executor, AuthenticationCallback)}
          */
         public CryptoObject getCryptoObject() {
             return (CryptoObject) super.getCryptoObject();
         }
+
+        /**
+         * Provides the type of authentication (e.g. device credential or biometric) that was
+         * requested from and successfully provided by the user.
+         *
+         * @return An integer value representing the authentication method used.
+         */
+        public @AuthenticationResultType int getAuthenticationType() {
+            return super.getAuthenticationType();
+        }
     }
 
     /**
diff --git a/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl b/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl
index c960049..1d43aa6 100644
--- a/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl
@@ -20,8 +20,8 @@
  * @hide
  */
 oneway interface IBiometricServiceReceiver {
-    // Notify BiometricPrompt that authentication was successful
-    void onAuthenticationSucceeded();
+    // Notify BiometricPrompt that authentication was successful.
+    void onAuthenticationSucceeded(int authenticationType);
     // Noties that authentication failed.
     void onAuthenticationFailed();
     // Notify BiometricPrompt that an error has occurred.
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index a5e0f04..456ebf2 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -557,9 +557,9 @@
                 dest.writeInt(vendorUuid.toString().length());
                 dest.writeString(vendorUuid.toString());
             }
+            dest.writeInt(version);
             dest.writeBlob(data);
             dest.writeTypedArray(keyphrases, flags);
-            dest.writeInt(version);
         }
 
         @Override
diff --git a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
index 7291419..03d29a3 100644
--- a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
+++ b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
@@ -370,6 +370,12 @@
         }
 
         @Override
+        public synchronized void onModuleDied() {
+            Message m = mHandler.obtainMessage(EVENT_SERVICE_DIED);
+            mHandler.sendMessage(m);
+        }
+
+        @Override
         public synchronized void binderDied() {
             Message m = mHandler.obtainMessage(EVENT_SERVICE_DIED);
             mHandler.sendMessage(m);
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index c523f65..8ba3131 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -662,7 +662,7 @@
      * {@hide}
      */
     @Deprecated
-    @UnsupportedAppUsage
+    @SystemApi
     public static final int TYPE_WIFI_P2P    = 13;
 
     /**
@@ -3622,14 +3622,26 @@
     /**
      * Helper function to request a network with a particular legacy type.
      *
-     * This is temporarily public @hide so it can be called by system code that uses the
-     * NetworkRequest API to request networks but relies on CONNECTIVITY_ACTION broadcasts for
-     * instead network notifications.
+     * @deprecated This is temporarily public for tethering to backwards compatibility that uses
+     * the NetworkRequest API to request networks with legacy type and relies on
+     * CONNECTIVITY_ACTION broadcasts instead of NetworkCallbacks. New caller should use
+     * {@link #requestNetwork(NetworkRequest, NetworkCallback, Handler)} instead.
      *
      * TODO: update said system code to rely on NetworkCallbacks and make this method private.
+
+     * @param request {@link NetworkRequest} describing this request.
+     * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note
+     *                        the callback must not be shared - it uniquely specifies this request.
+     * @param timeoutMs The time in milliseconds to attempt looking for a suitable network
+     *                  before {@link NetworkCallback#onUnavailable()} is called. The timeout must
+     *                  be a positive value (i.e. >0).
+     * @param legacyType to specify the network type(#TYPE_*).
+     * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
      *
      * @hide
      */
+    @SystemApi
+    @Deprecated
     public void requestNetwork(@NonNull NetworkRequest request,
             @NonNull NetworkCallback networkCallback, int timeoutMs, int legacyType,
             @NonNull Handler handler) {
diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl
index 385cb1d..72a6b39 100644
--- a/core/java/android/net/INetworkPolicyManager.aidl
+++ b/core/java/android/net/INetworkPolicyManager.aidl
@@ -57,9 +57,6 @@
     @UnsupportedAppUsage
     boolean getRestrictBackground();
 
-    /** Callback used to change internal state on tethering */
-    void onTetheringChanged(String iface, boolean tethering);
-
     /** Gets the restrict background status based on the caller's UID:
         1 - disabled
         2 - whitelisted
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index 2792c56..be8e561 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -21,6 +21,8 @@
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.net.util.LinkPropertiesUtils;
+import android.net.util.LinkPropertiesUtils.CompareResult;
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -84,36 +86,6 @@
     /**
      * @hide
      */
-    public static class CompareResult<T> {
-        public final List<T> removed = new ArrayList<>();
-        public final List<T> added = new ArrayList<>();
-
-        public CompareResult() {}
-
-        public CompareResult(Collection<T> oldItems, Collection<T> newItems) {
-            if (oldItems != null) {
-                removed.addAll(oldItems);
-            }
-            if (newItems != null) {
-                for (T newItem : newItems) {
-                    if (!removed.remove(newItem)) {
-                        added.add(newItem);
-                    }
-                }
-            }
-        }
-
-        @Override
-        public String toString() {
-            return "removed=[" + TextUtils.join(",", removed)
-                    + "] added=[" + TextUtils.join(",", added)
-                    + "]";
-        }
-    }
-
-    /**
-     * @hide
-     */
     @UnsupportedAppUsage(implicitMember =
             "values()[Landroid/net/LinkProperties$ProvisioningChange;")
     public enum ProvisioningChange {
@@ -1295,7 +1267,7 @@
      */
     @UnsupportedAppUsage
     public boolean isIdenticalInterfaceName(@NonNull LinkProperties target) {
-        return TextUtils.equals(getInterfaceName(), target.getInterfaceName());
+        return LinkPropertiesUtils.isIdenticalInterfaceName(target, this);
     }
 
     /**
@@ -1318,10 +1290,7 @@
      */
     @UnsupportedAppUsage
     public boolean isIdenticalAddresses(@NonNull LinkProperties target) {
-        Collection<InetAddress> targetAddresses = target.getAddresses();
-        Collection<InetAddress> sourceAddresses = getAddresses();
-        return (sourceAddresses.size() == targetAddresses.size()) ?
-                    sourceAddresses.containsAll(targetAddresses) : false;
+        return LinkPropertiesUtils.isIdenticalAddresses(target, this);
     }
 
     /**
@@ -1333,15 +1302,7 @@
      */
     @UnsupportedAppUsage
     public boolean isIdenticalDnses(@NonNull LinkProperties target) {
-        Collection<InetAddress> targetDnses = target.getDnsServers();
-        String targetDomains = target.getDomains();
-        if (mDomains == null) {
-            if (targetDomains != null) return false;
-        } else {
-            if (!mDomains.equals(targetDomains)) return false;
-        }
-        return (mDnses.size() == targetDnses.size()) ?
-                mDnses.containsAll(targetDnses) : false;
+        return LinkPropertiesUtils.isIdenticalDnses(target, this);
     }
 
     /**
@@ -1394,9 +1355,7 @@
      */
     @UnsupportedAppUsage
     public boolean isIdenticalRoutes(@NonNull LinkProperties target) {
-        Collection<RouteInfo> targetRoutes = target.getRoutes();
-        return (mRoutes.size() == targetRoutes.size()) ?
-                mRoutes.containsAll(targetRoutes) : false;
+        return LinkPropertiesUtils.isIdenticalRoutes(target, this);
     }
 
     /**
@@ -1408,8 +1367,7 @@
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     public boolean isIdenticalHttpProxy(@NonNull LinkProperties target) {
-        return getHttpProxy() == null ? target.getHttpProxy() == null :
-                getHttpProxy().equals(target.getHttpProxy());
+        return LinkPropertiesUtils.isIdenticalHttpProxy(target, this);
     }
 
     /**
@@ -1541,26 +1499,6 @@
     }
 
     /**
-     * Compares the addresses in this LinkProperties with another
-     * LinkProperties, examining only addresses on the base link.
-     *
-     * @param target a LinkProperties with the new list of addresses
-     * @return the differences between the addresses.
-     * @hide
-     */
-    public @NonNull CompareResult<LinkAddress> compareAddresses(@Nullable LinkProperties target) {
-        /*
-         * Duplicate the LinkAddresses into removed, we will be removing
-         * address which are common between mLinkAddresses and target
-         * leaving the addresses that are different. And address which
-         * are in target but not in mLinkAddresses are placed in the
-         * addedAddresses.
-         */
-        return new CompareResult<>(mLinkAddresses,
-                target != null ? target.getLinkAddresses() : null);
-    }
-
-    /**
      * Compares the DNS addresses in this LinkProperties with another
      * LinkProperties, examining only DNS addresses on the base link.
      *
diff --git a/core/java/android/net/MacAddress.java b/core/java/android/net/MacAddress.java
index 74c9aac..0e10c42 100644
--- a/core/java/android/net/MacAddress.java
+++ b/core/java/android/net/MacAddress.java
@@ -20,11 +20,11 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.net.util.MacAddressUtils;
 import android.net.wifi.WifiInfo;
 import android.os.Parcel;
 import android.os.Parcelable;
 
-import com.android.internal.util.BitUtils;
 import com.android.internal.util.Preconditions;
 
 import java.lang.annotation.Retention;
@@ -33,7 +33,6 @@
 import java.net.UnknownHostException;
 import java.security.SecureRandom;
 import java.util.Arrays;
-import java.util.Random;
 
 /**
  * Representation of a MAC address.
@@ -109,21 +108,13 @@
         if (equals(BROADCAST_ADDRESS)) {
             return TYPE_BROADCAST;
         }
-        if (isMulticastAddress()) {
+        if ((mAddr & MULTICAST_MASK) != 0) {
             return TYPE_MULTICAST;
         }
         return TYPE_UNICAST;
     }
 
     /**
-     * @return true if this MacAddress is a multicast address.
-     * @hide
-     */
-    public boolean isMulticastAddress() {
-        return (mAddr & MULTICAST_MASK) != 0;
-    }
-
-    /**
      * @return true if this MacAddress is a locally assigned address.
      */
     public boolean isLocallyAssigned() {
@@ -192,7 +183,7 @@
      * @hide
      */
     public static boolean isMacAddress(byte[] addr) {
-        return addr != null && addr.length == ETHER_ADDR_LEN;
+        return MacAddressUtils.isMacAddress(addr);
     }
 
     /**
@@ -261,26 +252,11 @@
     }
 
     private static byte[] byteAddrFromLongAddr(long addr) {
-        byte[] bytes = new byte[ETHER_ADDR_LEN];
-        int index = ETHER_ADDR_LEN;
-        while (index-- > 0) {
-            bytes[index] = (byte) addr;
-            addr = addr >> 8;
-        }
-        return bytes;
+        return MacAddressUtils.byteAddrFromLongAddr(addr);
     }
 
     private static long longAddrFromByteAddr(byte[] addr) {
-        Preconditions.checkNotNull(addr);
-        if (!isMacAddress(addr)) {
-            throw new IllegalArgumentException(
-                    Arrays.toString(addr) + " was not a valid MAC address");
-        }
-        long longAddr = 0;
-        for (byte b : addr) {
-            longAddr = (longAddr << 8) + BitUtils.uint8(b);
-        }
-        return longAddr;
+        return MacAddressUtils.longAddrFromByteAddr(addr);
     }
 
     // Internal conversion function equivalent to longAddrFromByteAddr(byteAddrFromStringAddr(addr))
@@ -350,50 +326,7 @@
      * @hide
      */
     public static @NonNull MacAddress createRandomUnicastAddressWithGoogleBase() {
-        return createRandomUnicastAddress(BASE_GOOGLE_MAC, new SecureRandom());
-    }
-
-    /**
-     * Returns a generated MAC address whose 46 bits, excluding the locally assigned bit and the
-     * unicast bit, are randomly selected.
-     *
-     * The locally assigned bit is always set to 1. The multicast bit is always set to 0.
-     *
-     * @return a random locally assigned, unicast MacAddress.
-     *
-     * @hide
-     */
-    public static @NonNull MacAddress createRandomUnicastAddress() {
-        return createRandomUnicastAddress(null, new SecureRandom());
-    }
-
-    /**
-     * Returns a randomly generated MAC address using the given Random object and the same
-     * OUI values as the given MacAddress.
-     *
-     * The locally assigned bit is always set to 1. The multicast bit is always set to 0.
-     *
-     * @param base a base MacAddress whose OUI is used for generating the random address.
-     *             If base == null then the OUI will also be randomized.
-     * @param r a standard Java Random object used for generating the random address.
-     * @return a random locally assigned MacAddress.
-     *
-     * @hide
-     */
-    public static @NonNull MacAddress createRandomUnicastAddress(MacAddress base, Random r) {
-        long addr;
-        if (base == null) {
-            addr = r.nextLong() & VALID_LONG_MASK;
-        } else {
-            addr = (base.mAddr & OUI_MASK) | (NIC_MASK & r.nextLong());
-        }
-        addr |= LOCALLY_ASSIGNED_MASK;
-        addr &= ~MULTICAST_MASK;
-        MacAddress mac = new MacAddress(addr);
-        if (mac.equals(DEFAULT_MAC_ADDRESS)) {
-            return createRandomUnicastAddress(base, r);
-        }
-        return mac;
+        return MacAddressUtils.createRandomUnicastAddress(BASE_GOOGLE_MAC, new SecureRandom());
     }
 
     // Convenience function for working around the lack of byte literals.
diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java
index d286204..aae9fd4 100644
--- a/core/java/android/net/NetworkAgent.java
+++ b/core/java/android/net/NetworkAgent.java
@@ -17,6 +17,8 @@
 package android.net;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.os.Build;
@@ -43,9 +45,11 @@
  *
  * @hide
  */
+@SystemApi
 public abstract class NetworkAgent {
-    // Guaranteed to be non-null, otherwise registerNetworkAgent() would have thrown
-    // an exception. Be careful in tests when mocking though.
+    /**
+     * The {@link Network} corresponding to this object.
+     */
     @NonNull
     public final Network network;
 
@@ -58,9 +62,14 @@
     private final ArrayList<Message>mPreConnectedQueue = new ArrayList<Message>();
     private volatile long mLastBwRefreshTime = 0;
     private static final long BW_REFRESH_MIN_WIN_MS = 500;
-    private boolean mPollLceScheduled = false;
-    private AtomicBoolean mPollLcePending = new AtomicBoolean(false);
-    public final int mProviderId;
+    private boolean mBandwidthUpdateScheduled = false;
+    private AtomicBoolean mBandwidthUpdatePending = new AtomicBoolean(false);
+
+    /**
+     * The ID of the {@link NetworkProvider} that created this object, or
+     * {@link NetworkProvider#ID_NONE} if unknown.
+     */
+    public final int providerId;
 
     private static final int BASE = Protocol.BASE_NETWORK_AGENT;
 
@@ -68,6 +77,7 @@
      * Sent by ConnectivityService to the NetworkAgent to inform it of
      * suspected connectivity problems on its network.  The NetworkAgent
      * should take steps to verify and correct connectivity.
+     * @hide
      */
     public static final int CMD_SUSPECT_BAD = BASE;
 
@@ -76,6 +86,7 @@
      * ConnectivityService to pass the current NetworkInfo (connection state).
      * Sent when the NetworkInfo changes, mainly due to change of state.
      * obj = NetworkInfo
+     * @hide
      */
     public static final int EVENT_NETWORK_INFO_CHANGED = BASE + 1;
 
@@ -83,6 +94,7 @@
      * Sent by the NetworkAgent to ConnectivityService to pass the current
      * NetworkCapabilties.
      * obj = NetworkCapabilities
+     * @hide
      */
     public static final int EVENT_NETWORK_CAPABILITIES_CHANGED = BASE + 2;
 
@@ -90,11 +102,14 @@
      * Sent by the NetworkAgent to ConnectivityService to pass the current
      * NetworkProperties.
      * obj = NetworkProperties
+     * @hide
      */
     public static final int EVENT_NETWORK_PROPERTIES_CHANGED = BASE + 3;
 
-    /* centralize place where base network score, and network score scaling, will be
+    /**
+     * Centralize the place where base network score, and network score scaling, will be
      * stored, so as we can consistently compare apple and oranges, or wifi, ethernet and LTE
+     * @hide
      */
     public static final int WIFI_BASE_SCORE = 60;
 
@@ -102,6 +117,7 @@
      * Sent by the NetworkAgent to ConnectivityService to pass the current
      * network score.
      * obj = network score Integer
+     * @hide
      */
     public static final int EVENT_NETWORK_SCORE_CHANGED = BASE + 4;
 
@@ -114,12 +130,33 @@
      * obj = Bundle containing map from {@code REDIRECT_URL_KEY} to {@code String}
      *       representing URL that Internet probe was redirect to, if it was redirected,
      *       or mapping to {@code null} otherwise.
+     * @hide
      */
     public static final int CMD_REPORT_NETWORK_STATUS = BASE + 7;
 
+
+    /**
+     * Network validation suceeded.
+     * Corresponds to {@link NetworkCapabilities.NET_CAPABILITY_VALIDATED}.
+     */
+    public static final int VALIDATION_STATUS_VALID = 1;
+
+    /**
+     * Network validation was attempted and failed. This may be received more than once as
+     * subsequent validation attempts are made.
+     */
+    public static final int VALIDATION_STATUS_NOT_VALID = 2;
+
+    // TODO: remove.
+    /** @hide */
     public static final int VALID_NETWORK = 1;
+    /** @hide */
     public static final int INVALID_NETWORK = 2;
 
+    /**
+     * The key for the redirect URL in the Bundle argument of {@code CMD_REPORT_NETWORK_STATUS}.
+     * @hide
+     */
     public static String REDIRECT_URL_KEY = "redirect URL";
 
      /**
@@ -128,6 +165,7 @@
      * CONNECTED so it can be given special treatment at that time.
      *
      * obj = boolean indicating whether to use this network even if unvalidated
+     * @hide
      */
     public static final int EVENT_SET_EXPLICITLY_SELECTED = BASE + 8;
 
@@ -138,12 +176,14 @@
      * responsibility to remember it.
      *
      * arg1 = 1 if true, 0 if false
+     * @hide
      */
     public static final int CMD_SAVE_ACCEPT_UNVALIDATED = BASE + 9;
 
     /**
      * Sent by ConnectivityService to the NetworkAgent to inform the agent to pull
      * the underlying network connection for updated bandwidth information.
+     * @hide
      */
     public static final int CMD_REQUEST_BANDWIDTH_UPDATE = BASE + 10;
 
@@ -156,6 +196,7 @@
      *   obj = KeepalivePacketData object describing the data to be sent
      *
      * Also used internally by ConnectivityService / KeepaliveTracker, with different semantics.
+     * @hide
      */
     public static final int CMD_START_SOCKET_KEEPALIVE = BASE + 11;
 
@@ -165,6 +206,7 @@
      * arg1 = slot number of the keepalive to stop.
      *
      * Also used internally by ConnectivityService / KeepaliveTracker, with different semantics.
+     * @hide
      */
     public static final int CMD_STOP_SOCKET_KEEPALIVE = BASE + 12;
 
@@ -178,6 +220,7 @@
      *
      * arg1 = slot number of the keepalive
      * arg2 = error code
+     * @hide
      */
     public static final int EVENT_SOCKET_KEEPALIVE = BASE + 13;
 
@@ -186,6 +229,7 @@
      * that when crossed should trigger a system wakeup and a NetworkCapabilities update.
      *
      *   obj = int[] describing signal strength thresholds.
+     * @hide
      */
     public static final int CMD_SET_SIGNAL_STRENGTH_THRESHOLDS = BASE + 14;
 
@@ -193,6 +237,7 @@
      * Sent by ConnectivityService to the NeworkAgent to inform the agent to avoid
      * automatically reconnecting to this network (e.g. via autojoin).  Happens
      * when user selects "No" option on the "Stay connected?" dialog box.
+     * @hide
      */
     public static final int CMD_PREVENT_AUTOMATIC_RECONNECT = BASE + 15;
 
@@ -205,6 +250,7 @@
      * This does not happen with UDP, so this message is TCP-specific.
      * arg1 = slot number of the keepalive to filter for.
      * obj = the keepalive packet to send repeatedly.
+     * @hide
      */
     public static final int CMD_ADD_KEEPALIVE_PACKET_FILTER = BASE + 16;
 
@@ -212,6 +258,7 @@
      * Sent by the KeepaliveTracker to NetworkAgent to remove a packet filter. See
      * {@link #CMD_ADD_KEEPALIVE_PACKET_FILTER}.
      * arg1 = slot number of the keepalive packet filter to remove.
+     * @hide
      */
     public static final int CMD_REMOVE_KEEPALIVE_PACKET_FILTER = BASE + 17;
 
@@ -219,27 +266,32 @@
     // of dependent changes that would conflict throughout the automerger graph. Having these
     // temporarily helps with the process of going through with all these dependent changes across
     // the entire tree.
+    /** @hide TODO: decide which of these to expose. */
     public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
             NetworkCapabilities nc, LinkProperties lp, int score) {
         this(looper, context, logTag, ni, nc, lp, score, null, NetworkProvider.ID_NONE);
     }
+
+    /** @hide TODO: decide which of these to expose. */
     public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
             NetworkCapabilities nc, LinkProperties lp, int score, NetworkAgentConfig config) {
         this(looper, context, logTag, ni, nc, lp, score, config, NetworkProvider.ID_NONE);
     }
 
+    /** @hide TODO: decide which of these to expose. */
     public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
             NetworkCapabilities nc, LinkProperties lp, int score, int providerId) {
         this(looper, context, logTag, ni, nc, lp, score, null, providerId);
     }
 
+    /** @hide TODO: decide which of these to expose. */
     public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
             NetworkCapabilities nc, LinkProperties lp, int score, NetworkAgentConfig config,
             int providerId) {
         mHandler = new NetworkAgentHandler(looper);
         LOG_TAG = logTag;
         mContext = context;
-        mProviderId = providerId;
+        this.providerId = providerId;
         if (ni == null || nc == null || lp == null) {
             throw new IllegalArgumentException();
         }
@@ -287,7 +339,7 @@
                 case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
                     if (DBG) log("NetworkAgent channel lost");
                     // let the client know CS is done with us.
-                    unwanted();
+                    onNetworkUnwanted();
                     synchronized (mPreConnectedQueue) {
                         mAsyncChannel = null;
                     }
@@ -303,16 +355,16 @@
                         log("CMD_REQUEST_BANDWIDTH_UPDATE request received.");
                     }
                     if (currentTimeMs >= (mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS)) {
-                        mPollLceScheduled = false;
-                        if (!mPollLcePending.getAndSet(true)) {
-                            pollLceData();
+                        mBandwidthUpdateScheduled = false;
+                        if (!mBandwidthUpdatePending.getAndSet(true)) {
+                            onBandwidthUpdateRequested();
                         }
                     } else {
                         // deliver the request at a later time rather than discard it completely.
-                        if (!mPollLceScheduled) {
+                        if (!mBandwidthUpdateScheduled) {
                             long waitTime = mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS
                                     - currentTimeMs + 1;
-                            mPollLceScheduled = sendEmptyMessageDelayed(
+                            mBandwidthUpdateScheduled = sendEmptyMessageDelayed(
                                     CMD_REQUEST_BANDWIDTH_UPDATE, waitTime);
                         }
                     }
@@ -325,19 +377,20 @@
                                 + (msg.arg1 == VALID_NETWORK ? "VALID, " : "INVALID, ")
                                 + redirectUrl);
                     }
-                    networkStatus(msg.arg1, redirectUrl);
+                    onValidationStatus(msg.arg1 /* status */, redirectUrl);
                     break;
                 }
                 case CMD_SAVE_ACCEPT_UNVALIDATED: {
-                    saveAcceptUnvalidated(msg.arg1 != 0);
+                    onSaveAcceptUnvalidated(msg.arg1 != 0);
                     break;
                 }
                 case CMD_START_SOCKET_KEEPALIVE: {
-                    startSocketKeepalive(msg);
+                    onStartSocketKeepalive(msg.arg1 /* slot */, msg.arg2 /* interval */,
+                            (KeepalivePacketData) msg.obj /* packet */);
                     break;
                 }
                 case CMD_STOP_SOCKET_KEEPALIVE: {
-                    stopSocketKeepalive(msg);
+                    onStopSocketKeepalive(msg.arg1 /* slot */);
                     break;
                 }
 
@@ -350,19 +403,20 @@
                     for (int i = 0; i < intThresholds.length; i++) {
                         intThresholds[i] = thresholds.get(i);
                     }
-                    setSignalStrengthThresholds(intThresholds);
+                    onSignalStrengthThresholdsUpdated(intThresholds);
                     break;
                 }
                 case CMD_PREVENT_AUTOMATIC_RECONNECT: {
-                    preventAutomaticReconnect();
+                    onAutomaticReconnectDisabled();
                     break;
                 }
                 case CMD_ADD_KEEPALIVE_PACKET_FILTER: {
-                    addKeepalivePacketFilter(msg);
+                    onAddKeepalivePacketFilter(msg.arg1 /* slot */,
+                            (KeepalivePacketData) msg.obj /* packet */);
                     break;
                 }
                 case CMD_REMOVE_KEEPALIVE_PACKET_FILTER: {
-                    removeKeepalivePacketFilter(msg);
+                    onRemoveKeepalivePacketFilter(msg.arg1 /* slot */);
                     break;
                 }
             }
@@ -397,14 +451,16 @@
     }
 
     /**
-     * Called by the bearer code when it has new LinkProperties data.
+     * Must be called by the agent when the network's {@link LinkProperties} change.
+     * @param linkProperties the new LinkProperties.
      */
-    public void sendLinkProperties(LinkProperties linkProperties) {
+    public void sendLinkProperties(@NonNull LinkProperties linkProperties) {
         queueOrSendMessage(EVENT_NETWORK_PROPERTIES_CHANGED, new LinkProperties(linkProperties));
     }
 
     /**
-     * Called by the bearer code when it has new NetworkInfo data.
+     * Must be called by the agent when it has a new NetworkInfo object.
+     * @hide TODO: expose something better.
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     public void sendNetworkInfo(NetworkInfo networkInfo) {
@@ -412,17 +468,19 @@
     }
 
     /**
-     * Called by the bearer code when it has new NetworkCapabilities data.
+     * Must be called by the agent when the network's {@link NetworkCapabilities} change.
+     * @param networkCapabilities the new NetworkCapabilities.
      */
-    public void sendNetworkCapabilities(NetworkCapabilities networkCapabilities) {
-        mPollLcePending.set(false);
+    public void sendNetworkCapabilities(@NonNull NetworkCapabilities networkCapabilities) {
+        mBandwidthUpdatePending.set(false);
         mLastBwRefreshTime = System.currentTimeMillis();
         queueOrSendMessage(EVENT_NETWORK_CAPABILITIES_CHANGED,
                 new NetworkCapabilities(networkCapabilities));
     }
 
     /**
-     * Called by the bearer code when it has a new score for this network.
+     * Must be called by the agent to update the score of this network.
+     * @param score the new score.
      */
     public void sendNetworkScore(int score) {
         if (score < 0) {
@@ -434,14 +492,16 @@
     }
 
     /**
-     * Called by the bearer code when it has a new NetworkScore for this network.
+     * Must be called by the agent when it has a new {@link NetworkScore} for this network.
+     * @param ns the new score.
+     * @hide TODO: unhide the NetworkScore class, and rename to sendNetworkScore.
      */
     public void updateScore(@NonNull NetworkScore ns) {
         queueOrSendMessage(EVENT_NETWORK_SCORE_CHANGED, new NetworkScore(ns));
     }
 
     /**
-     * Called by the bearer to indicate this network was manually selected by the user.
+     * Must be called by the agent to indicate this network was manually selected by the user.
      * This should be called before the NetworkInfo is marked CONNECTED so that this
      * Network can be given special treatment at that time. If {@code acceptUnvalidated} is
      * {@code true}, then the system will switch to this network. If it is {@code false} and the
@@ -450,15 +510,16 @@
      * {@link #saveAcceptUnvalidated} to persist the user's choice. Thus, if the transport ever
      * calls this method with {@code acceptUnvalidated} set to {@code false}, it must also implement
      * {@link #saveAcceptUnvalidated} to respect the user's choice.
+     * @hide should move to NetworkAgentConfig.
      */
     public void explicitlySelected(boolean acceptUnvalidated) {
         explicitlySelected(true /* explicitlySelected */, acceptUnvalidated);
     }
 
     /**
-     * Called by the bearer to indicate whether the network was manually selected by the user.
-     * This should be called before the NetworkInfo is marked CONNECTED so that this
-     * Network can be given special treatment at that time.
+     * Must be called by the agent to indicate whether the network was manually selected by the
+     * user. This should be called before the network becomes connected, so it can be given
+     * special treatment when it does.
      *
      * If {@code explicitlySelected} is {@code true}, and {@code acceptUnvalidated} is {@code true},
      * then the system will switch to this network. If {@code explicitlySelected} is {@code true}
@@ -473,6 +534,7 @@
      * {@code true}, the system will interpret this as the user having accepted partial connectivity
      * on this network. Thus, the system will switch to the network and consider it validated even
      * if it only provides partial connectivity, but the network is not otherwise treated specially.
+     * @hide should move to NetworkAgentConfig.
      */
     public void explicitlySelected(boolean explicitlySelected, boolean acceptUnvalidated) {
         queueOrSendMessage(EVENT_SET_EXPLICITLY_SELECTED,
@@ -486,73 +548,126 @@
      * as well, either canceling NetworkRequests or altering their score such that this
      * network won't be immediately requested again.
      */
-    abstract protected void unwanted();
+    public void onNetworkUnwanted() {
+        unwanted();
+    }
+    /** @hide TODO delete once subclasses have moved to onNetworkUnwanted. */
+    protected void unwanted() {
+    }
 
     /**
      * Called when ConnectivityService request a bandwidth update. The parent factory
      * shall try to overwrite this method and produce a bandwidth update if capable.
      */
+    public void onBandwidthUpdateRequested() {
+        pollLceData();
+    }
+    /** @hide TODO delete once subclasses have moved to onBandwidthUpdateRequested. */
     protected void pollLceData() {
     }
 
     /**
      * Called when the system determines the usefulness of this network.
      *
-     * Networks claiming internet connectivity will have their internet
-     * connectivity verified.
+     * The system attempts to validate Internet connectivity on networks that provide the
+     * {@link NetworkCapabilities#NET_CAPABILITY_INTERNET} capability.
      *
      * Currently there are two possible values:
-     * {@code VALID_NETWORK} if the system is happy with the connection,
-     * {@code INVALID_NETWORK} if the system is not happy.
-     * TODO - add indications of captive portal-ness and related success/failure,
-     * ie, CAPTIVE_SUCCESS_NETWORK, CAPTIVE_NETWORK for successful login and detection
+     * {@code VALIDATION_STATUS_VALID} if Internet connectivity was validated,
+     * {@code VALIDATION_STATUS_NOT_VALID} if Internet connectivity was not validated.
      *
-     * This may be called multiple times as the network status changes and may
-     * generate false negatives if we lose ip connectivity before the link is torn down.
+     * This may be called multiple times as network status changes, or if there are multiple
+     * subsequent attempts to validate connectivity that fail.
      *
-     * @param status one of {@code VALID_NETWORK} or {@code INVALID_NETWORK}.
-     * @param redirectUrl If the Internet probe was redirected, this is the destination it was
-     *         redirected to, otherwise {@code null}.
+     * @param status one of {@code VALIDATION_STATUS_VALID} or {@code VALIDATION_STATUS_NOT_VALID}.
+     * @param redirectUrl If Internet connectivity is being redirected (e.g., on a captive portal),
+     *        this is the destination the probes are being redirected to, otherwise {@code null}.
      */
+    public void onValidationStatus(int status, @Nullable String redirectUrl) {
+        networkStatus(status, redirectUrl);
+    }
+    /** @hide TODO delete once subclasses have moved to onValidationStatus */
     protected void networkStatus(int status, String redirectUrl) {
     }
 
+
     /**
      * Called when the user asks to remember the choice to use this network even if unvalidated.
      * The transport is responsible for remembering the choice, and the next time the user connects
      * to the network, should explicitlySelected with {@code acceptUnvalidated} set to {@code true}.
      * This method will only be called if {@link #explicitlySelected} was called with
      * {@code acceptUnvalidated} set to {@code false}.
+     * @param accept whether the user wants to use the network even if unvalidated.
      */
+    public void onSaveAcceptUnvalidated(boolean accept) {
+        saveAcceptUnvalidated(accept);
+    }
+    /** @hide TODO delete once subclasses have moved to onSaveAcceptUnvalidated */
     protected void saveAcceptUnvalidated(boolean accept) {
     }
 
     /**
      * Requests that the network hardware send the specified packet at the specified interval.
+     *
+     * @param slot the hardware slot on which to start the keepalive.
+     * @param intervalSeconds the interval between packets
+     * @param packet the packet to send.
      */
+    public void onStartSocketKeepalive(int slot, int intervalSeconds,
+            @NonNull KeepalivePacketData packet) {
+        Message msg = mHandler.obtainMessage(CMD_START_SOCKET_KEEPALIVE, slot, intervalSeconds,
+                packet);
+        startSocketKeepalive(msg);
+        msg.recycle();
+    }
+    /** @hide TODO delete once subclasses have moved to onStartSocketKeepalive */
     protected void startSocketKeepalive(Message msg) {
         onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_UNSUPPORTED);
     }
 
     /**
-     * Requests that the network hardware send the specified packet at the specified interval.
+     * Requests that the network hardware stop a previously-started keepalive.
+     *
+     * @param slot the hardware slot on which to stop the keepalive.
      */
+    public void onStopSocketKeepalive(int slot) {
+        Message msg = mHandler.obtainMessage(CMD_STOP_SOCKET_KEEPALIVE, slot, 0, null);
+        stopSocketKeepalive(msg);
+        msg.recycle();
+    }
+    /** @hide TODO delete once subclasses have moved to onStopSocketKeepalive */
     protected void stopSocketKeepalive(Message msg) {
         onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_UNSUPPORTED);
     }
 
     /**
-     * Called by the network when a socket keepalive event occurs.
+     * Must be called by the agent when a socket keepalive event occurs.
+     *
+     * @param slot the hardware slot on which the event occurred.
+     * @param event the event that occurred.
      */
+    public void sendSocketKeepaliveEvent(int slot, int event) {
+        queueOrSendMessage(EVENT_SOCKET_KEEPALIVE, slot, event);
+    }
+    /** @hide TODO delete once callers have moved to sendSocketKeepaliveEvent */
     public void onSocketKeepaliveEvent(int slot, int reason) {
-        queueOrSendMessage(EVENT_SOCKET_KEEPALIVE, slot, reason);
+        sendSocketKeepaliveEvent(slot, reason);
     }
 
     /**
      * Called by ConnectivityService to add specific packet filter to network hardware to block
-     * ACKs matching the sent keepalive packets. Implementations that support this feature must
-     * override this method.
+     * replies (e.g., TCP ACKs) matching the sent keepalive packets. Implementations that support
+     * this feature must override this method.
+     *
+     * @param slot the hardware slot on which the keepalive should be sent.
+     * @param packet the packet that is being sent.
      */
+    public void onAddKeepalivePacketFilter(int slot, @NonNull KeepalivePacketData packet) {
+        Message msg = mHandler.obtainMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER, slot, 0, packet);
+        addKeepalivePacketFilter(msg);
+        msg.recycle();
+    }
+    /** @hide TODO delete once subclasses have moved to onAddKeepalivePacketFilter */
     protected void addKeepalivePacketFilter(Message msg) {
     }
 
@@ -560,14 +675,28 @@
      * Called by ConnectivityService to remove a packet filter installed with
      * {@link #addKeepalivePacketFilter(Message)}. Implementations that support this feature
      * must override this method.
+     *
+     * @param slot the hardware slot on which the keepalive is being sent.
      */
+    public void onRemoveKeepalivePacketFilter(int slot) {
+        Message msg = mHandler.obtainMessage(CMD_REMOVE_KEEPALIVE_PACKET_FILTER, slot, 0, null);
+        removeKeepalivePacketFilter(msg);
+        msg.recycle();
+    }
+    /** @hide TODO delete once subclasses have moved to onRemoveKeepalivePacketFilter */
     protected void removeKeepalivePacketFilter(Message msg) {
     }
 
     /**
      * Called by ConnectivityService to inform this network transport of signal strength thresholds
      * that when crossed should trigger a system wakeup and a NetworkCapabilities update.
+     *
+     * @param thresholds the array of thresholds that should trigger wakeups.
      */
+    public void onSignalStrengthThresholdsUpdated(@NonNull int[] thresholds) {
+        setSignalStrengthThresholds(thresholds);
+    }
+    /** @hide TODO delete once subclasses have moved to onSetSignalStrengthThresholds */
     protected void setSignalStrengthThresholds(int[] thresholds) {
     }
 
@@ -577,9 +706,14 @@
      * responsible for making sure the device does not automatically reconnect to the same network
      * after the {@code unwanted} call.
      */
+    public void onAutomaticReconnectDisabled() {
+        preventAutomaticReconnect();
+    }
+    /** @hide TODO delete once subclasses have moved to onAutomaticReconnectDisabled */
     protected void preventAutomaticReconnect() {
     }
 
+    /** @hide */
     protected void log(String s) {
         Log.d(LOG_TAG, "NetworkAgent: " + s);
     }
diff --git a/core/java/android/net/NetworkFactory.java b/core/java/android/net/NetworkFactory.java
index 824ddb8..e271037 100644
--- a/core/java/android/net/NetworkFactory.java
+++ b/core/java/android/net/NetworkFactory.java
@@ -115,13 +115,6 @@
      */
     private static final int CMD_SET_FILTER = BASE + 3;
 
-    /**
-     * Sent by NetworkFactory to ConnectivityService to indicate that a request is
-     * unfulfillable.
-     * @see #releaseRequestAsUnfulfillableByAnyFactory(NetworkRequest).
-     */
-    public static final int EVENT_UNFULFILLABLE_REQUEST = BASE + 4;
-
     private final Context mContext;
     private final ArrayList<Message> mPreConnectedQueue = new ArrayList<Message>();
     private final String LOG_TAG;
diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
index 9731f3c..301d203 100644
--- a/core/java/android/net/NetworkRequest.java
+++ b/core/java/android/net/NetworkRequest.java
@@ -300,22 +300,34 @@
          * this without a single transport set will generate an exception, as will
          * subsequently adding or removing transports after this is set.
          * </p>
-         * The interpretation of this {@code String} is bearer specific and bearers that use
-         * it should document their particulars.  For example, Bluetooth may use some sort of
-         * device id while WiFi could used ssid and/or bssid.  Cellular may use carrier spn.
+         * If the {@code networkSpecifier} is provided, it shall be interpreted as follows:
+         * <ul>
+         * <li>If the specifier can be parsed as an integer, it will be treated as a
+         * {@link android.net TelephonyNetworkSpecifier}, and the provided integer will be
+         * interpreted as a SubscriptionId.
+         * <li>If the value is an ethernet interface name, it will be treated as such.
+         * <li>For all other cases, the behavior is undefined.
+         * </ul>
          *
-         * @param networkSpecifier An {@code String} of opaque format used to specify the bearer
-         *                         specific network specifier where the bearer has a choice of
-         *                         networks.
+         * @param networkSpecifier A {@code String} of either a SubscriptionId in cellular
+         *                         network request or an ethernet interface name in ethernet
+         *                         network request.
+         *
+         * @deprecated Use {@link #setNetworkSpecifier(NetworkSpecifier)} instead.
          */
+        @Deprecated
         public Builder setNetworkSpecifier(String networkSpecifier) {
-            /*
-             * A StringNetworkSpecifier does not accept null or empty ("") strings. When network
-             * specifiers were strings a null string and an empty string were considered equivalent.
-             * Hence no meaning is attached to a null or empty ("") string.
-             */
-            return setNetworkSpecifier(TextUtils.isEmpty(networkSpecifier) ? null
-                    : new StringNetworkSpecifier(networkSpecifier));
+            try {
+                int subId = Integer.parseInt(networkSpecifier);
+                return setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
+                        .setSubscriptionId(subId).build());
+            } catch (NumberFormatException nfe) {
+                // A StringNetworkSpecifier does not accept null or empty ("") strings. When network
+                // specifiers were strings a null string and an empty string were considered
+                // equivalent. Hence no meaning is attached to a null or empty ("") string.
+                return setNetworkSpecifier(TextUtils.isEmpty(networkSpecifier) ? null
+                        : new StringNetworkSpecifier(networkSpecifier));
+            }
         }
 
         /**
@@ -455,6 +467,19 @@
     }
 
     /**
+     * Returns true iff. the capabilities requested in this NetworkRequest are satisfied by the
+     * provided {@link NetworkCapabilities}.
+     *
+     * @param nc Capabilities that should satisfy this NetworkRequest. null capabilities do not
+     *           satisfy any request.
+     * @hide
+     */
+    @SystemApi
+    public boolean satisfiedBy(@Nullable NetworkCapabilities nc) {
+        return networkCapabilities.satisfiedByNetworkCapabilities(nc);
+    }
+
+    /**
      * @see Builder#addTransportType(int)
      */
     public boolean hasTransport(@Transport int transportType) {
diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java
index 428a4ea..c233ec0 100644
--- a/core/java/android/net/NetworkScoreManager.java
+++ b/core/java/android/net/NetworkScoreManager.java
@@ -403,7 +403,7 @@
      * @throws SecurityException if the caller does not hold the
      *         {@link permission#REQUEST_NETWORK_SCORES} permission.
      * @throws IllegalArgumentException if a score cache is already registered for this type.
-     * @deprecated equivalent to registering for cache updates with CACHE_FILTER_NONE.
+     * @deprecated equivalent to registering for cache updates with {@link #SCORE_FILTER_NONE}.
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES)
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index 08cc4e2..779f7bc 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -31,7 +31,6 @@
 import java.io.FileDescriptor;
 import java.math.BigInteger;
 import java.net.Inet4Address;
-import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.SocketException;
 import java.net.UnknownHostException;
@@ -313,15 +312,6 @@
     }
 
     /**
-     * Check if IP address type is consistent between two InetAddress.
-     * @return true if both are the same type.  False otherwise.
-     */
-    public static boolean addressTypeMatches(InetAddress left, InetAddress right) {
-        return (((left instanceof Inet4Address) && (right instanceof Inet4Address)) ||
-                ((left instanceof Inet6Address) && (right instanceof Inet6Address)));
-    }
-
-    /**
      * Convert a 32 char hex string into a Inet6Address.
      * throws a runtime exception if the string isn't 32 chars, isn't hex or can't be
      * made into an Inet6Address
diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java
index ea6002c..e088094 100644
--- a/core/java/android/net/RouteInfo.java
+++ b/core/java/android/net/RouteInfo.java
@@ -22,6 +22,7 @@
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.net.util.NetUtils;
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -441,21 +442,7 @@
     @UnsupportedAppUsage
     @Nullable
     public static RouteInfo selectBestRoute(Collection<RouteInfo> routes, InetAddress dest) {
-        if ((routes == null) || (dest == null)) return null;
-
-        RouteInfo bestRoute = null;
-        // pick a longest prefix match under same address type
-        for (RouteInfo route : routes) {
-            if (NetworkUtils.addressTypeMatches(route.mDestination.getAddress(), dest)) {
-                if ((bestRoute != null) &&
-                        (bestRoute.mDestination.getPrefixLength() >=
-                        route.mDestination.getPrefixLength())) {
-                    continue;
-                }
-                if (route.matches(dest)) bestRoute = route;
-            }
-        }
-        return bestRoute;
+        return NetUtils.selectBestRoute(routes, dest);
     }
 
     /**
diff --git a/core/java/android/net/SocketKeepalive.java b/core/java/android/net/SocketKeepalive.java
index fb224fb..fc9a8f6 100644
--- a/core/java/android/net/SocketKeepalive.java
+++ b/core/java/android/net/SocketKeepalive.java
@@ -20,6 +20,7 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.os.Binder;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
@@ -53,7 +54,11 @@
 public abstract class SocketKeepalive implements AutoCloseable {
     static final String TAG = "SocketKeepalive";
 
-    /** @hide */
+    /**
+     * No errors.
+     * @hide
+     */
+    @SystemApi
     public static final int SUCCESS = 0;
 
     /** @hide */
diff --git a/core/java/android/net/TelephonyNetworkSpecifier.java b/core/java/android/net/TelephonyNetworkSpecifier.java
new file mode 100644
index 0000000..726f770
--- /dev/null
+++ b/core/java/android/net/TelephonyNetworkSpecifier.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * NetworkSpecifier object for cellular network request. Apps should use the
+ * {@link TelephonyNetworkSpecifier.Builder} class to create an instance.
+ */
+public final class TelephonyNetworkSpecifier extends NetworkSpecifier implements Parcelable {
+
+    private final int mSubId;
+
+    /**
+     * Return the subscription Id of current TelephonyNetworkSpecifier object.
+     *
+     * @return The subscription id.
+     */
+    public int getSubscriptionId() {
+        return mSubId;
+    }
+
+    /**
+     * @hide
+     */
+    public TelephonyNetworkSpecifier(int subId) {
+        this.mSubId = subId;
+    }
+
+    public static final @NonNull Creator<TelephonyNetworkSpecifier> CREATOR =
+            new Creator<TelephonyNetworkSpecifier>() {
+                @Override
+                public TelephonyNetworkSpecifier createFromParcel(Parcel in) {
+                    int subId = in.readInt();
+                    return new TelephonyNetworkSpecifier(subId);
+                }
+
+                @Override
+                public TelephonyNetworkSpecifier[] newArray(int size) {
+                    return new TelephonyNetworkSpecifier[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mSubId);
+    }
+
+    @Override
+    public int hashCode() {
+        return mSubId;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof TelephonyNetworkSpecifier)) {
+            return false;
+        }
+
+        TelephonyNetworkSpecifier lhs = (TelephonyNetworkSpecifier) obj;
+        return mSubId == lhs.mSubId;
+    }
+
+    @Override
+    public String toString() {
+        return new StringBuilder()
+                .append("TelephonyNetworkSpecifier [")
+                .append("mSubId = ").append(mSubId)
+                .append("]")
+                .toString();
+    }
+
+    /** @hide */
+    @Override
+    public boolean satisfiedBy(NetworkSpecifier other) {
+        // Any generic requests should be satisfied by a specific telephony network.
+        // For simplicity, we treat null same as MatchAllNetworkSpecifier
+        return equals(other) || other == null || other instanceof MatchAllNetworkSpecifier;
+    }
+
+
+    /**
+     * Builder to create {@link TelephonyNetworkSpecifier} object.
+     */
+    public static final class Builder {
+        // Integer.MIN_VALUE which is not a valid subId, services as the sentinel to check if
+        // subId was set
+        private static final int SENTINEL_SUB_ID = Integer.MIN_VALUE;
+
+        private int mSubId;
+
+        public Builder() {
+            mSubId = SENTINEL_SUB_ID;
+        }
+
+        /**
+         * Set the subscription id.
+         *
+         * @param subId The subscription Id.
+         * @return Instance of {@link Builder} to enable the chaining of the builder method.
+         */
+        public @NonNull Builder setSubscriptionId(int subId) {
+            mSubId = subId;
+            return this;
+        }
+
+        /**
+         * Create a NetworkSpecifier for the cellular network request.
+         *
+         * @return TelephonyNetworkSpecifier object.
+         * @throws IllegalArgumentException when subscription Id is not provided through
+         *         {@link #setSubscriptionId(int)}.
+         */
+        public @NonNull TelephonyNetworkSpecifier build() {
+            if (mSubId == SENTINEL_SUB_ID) {
+                throw new IllegalArgumentException("Subscription Id is not provided.");
+            }
+            return new TelephonyNetworkSpecifier(mSubId);
+        }
+    }
+}
diff --git a/core/java/android/os/IIncidentDumpCallback.aidl b/core/java/android/os/IIncidentDumpCallback.aidl
new file mode 100644
index 0000000..09b5b01
--- /dev/null
+++ b/core/java/android/os/IIncidentDumpCallback.aidl
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.os.ParcelFileDescriptor;
+
+/**
+ * Callback from IIncidentManager to dump an extended section.
+ *
+ * @hide
+ */
+oneway interface IIncidentDumpCallback {
+    /**
+     * Dumps section data to the given ParcelFileDescriptor.
+     */
+    void onDumpSection(in ParcelFileDescriptor fd);
+}
diff --git a/core/java/android/os/IIncidentManager.aidl b/core/java/android/os/IIncidentManager.aidl
index 7e1b1e0..923234e 100644
--- a/core/java/android/os/IIncidentManager.aidl
+++ b/core/java/android/os/IIncidentManager.aidl
@@ -17,6 +17,7 @@
 package android.os;
 
 import android.os.IIncidentReportStatusListener;
+import android.os.IIncidentDumpCallback;
 import android.os.IncidentManager;
 import android.os.IncidentReportArgs;
 
@@ -52,6 +53,19 @@
             @nullable IIncidentReportStatusListener listener);
 
     /**
+     * Register a section callback with the given id and name. The callback function
+     * will be invoked when an incident report with all sections or sections matching
+     * the given id is being taken.
+     */
+    oneway void registerSection(int id, String name, IIncidentDumpCallback callback);
+
+    /**
+     * Unregister a section callback associated with the given id. The section must be
+     * previously registered with registerSection(int, String, IIncidentDumpCallback).
+     */
+    oneway void unregisterSection(int id);
+
+    /**
      * Tell the incident daemon that the android system server is up and running.
      */
     oneway void systemRunning();
diff --git a/core/java/android/os/IVibratorService.aidl b/core/java/android/os/IVibratorService.aidl
index 7b2d148..416d692 100644
--- a/core/java/android/os/IVibratorService.aidl
+++ b/core/java/android/os/IVibratorService.aidl
@@ -24,7 +24,7 @@
 {
     boolean hasVibrator();
     boolean hasAmplitudeControl();
-    boolean setAlwaysOnEffect(int id, in VibrationEffect effect,
+    boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, in VibrationEffect effect,
             in VibrationAttributes attributes);
     void vibrate(int uid, String opPkg, in VibrationEffect effect,
             in VibrationAttributes attributes, String reason, IBinder token);
diff --git a/core/java/android/os/IncidentManager.java b/core/java/android/os/IncidentManager.java
index 09e1c0f..f6563eb 100644
--- a/core/java/android/os/IncidentManager.java
+++ b/core/java/android/os/IncidentManager.java
@@ -31,6 +31,7 @@
 import java.io.Closeable;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -421,6 +422,39 @@
     }
 
     /**
+     * Callback for dumping an extended (usually vendor-supplied) incident report section
+     *
+     * @see #registerSection
+     * @see #unregisterSection
+     *
+     * @hide
+     */
+    public static class DumpCallback {
+        private Executor mExecutor;
+
+        IIncidentDumpCallback.Stub mBinder = new IIncidentDumpCallback.Stub() {
+            @Override
+            public void onDumpSection(ParcelFileDescriptor pfd) {
+                if (mExecutor != null) {
+                    mExecutor.execute(() -> {
+                        DumpCallback.this.onDumpSection(
+                                new ParcelFileDescriptor.AutoCloseOutputStream(pfd));
+                    });
+                } else {
+                    DumpCallback.this.onDumpSection(
+                            new ParcelFileDescriptor.AutoCloseOutputStream(pfd));
+                }
+            }
+        };
+
+        /**
+         * Called when incidentd requests to dump this section.
+         */
+        public void onDumpSection(OutputStream out) {
+        }
+    }
+
+    /**
      * @hide
      */
     public IncidentManager(Context context) {
@@ -528,6 +562,61 @@
     }
 
     /**
+     * Register a callback to dump an extended incident report section with the given id and name.
+     * The callback function will be invoked when an incident report with all sections or sections
+     * matching the given id is being taken.
+     *
+     * @hide
+     */
+    public void registerSection(int id, String name, @NonNull DumpCallback callback) {
+        registerSection(id, name, mContext.getMainExecutor(), callback);
+    }
+
+    /**
+     * Register a callback to dump an extended incident report section with the given id and name,
+     * running on the supplied executor.
+     *
+     * @hide
+     */
+    public void registerSection(int id, String name, @NonNull @CallbackExecutor Executor executor,
+            @NonNull DumpCallback callback) {
+        try {
+            if (callback.mExecutor != null) {
+                throw new RuntimeException("Do not reuse DumpCallback objects when calling"
+                        + " registerSection");
+            }
+            callback.mExecutor = executor;
+            final IIncidentManager service = getIIncidentManagerLocked();
+            if (service == null) {
+                Slog.e(TAG, "registerSection can't find incident binder service");
+                return;
+            }
+            service.registerSection(id, name, callback.mBinder);
+        } catch (RemoteException ex) {
+            Slog.e(TAG, "registerSection failed", ex);
+        }
+    }
+
+    /**
+     * Unregister an extended section dump function. The section must be previously registered with
+     * {@link #registerSection(int, String, DumpCallback)}
+     *
+     * @hide
+     */
+    public void unregisterSection(int id) {
+        try {
+            final IIncidentManager service = getIIncidentManagerLocked();
+            if (service == null) {
+                Slog.e(TAG, "unregisterSection can't find incident binder service");
+                return;
+            }
+            service.unregisterSection(id);
+        } catch (RemoteException ex) {
+            Slog.e(TAG, "unregisterSection failed", ex);
+        }
+    }
+
+    /**
      * Get the incident reports that are available for upload for the supplied
      * broadcast recevier.
      *
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index c1542c7..8050454 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -70,14 +70,15 @@
     }
 
     @Override
-    public boolean setAlwaysOnEffect(int id, VibrationEffect effect, AudioAttributes attributes) {
+    public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, VibrationEffect effect,
+            AudioAttributes attributes) {
         if (mService == null) {
             Log.w(TAG, "Failed to set always-on effect; no vibrator service.");
             return false;
         }
         try {
             VibrationAttributes atr = new VibrationAttributes.Builder(attributes, effect).build();
-            return mService.setAlwaysOnEffect(id, effect, atr);
+            return mService.setAlwaysOnEffect(uid, opPkg, alwaysOnId, effect, atr);
         } catch (RemoteException e) {
             Log.w(TAG, "Failed to set always-on effect.", e);
         }
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index d8fadfb..2eaefca 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -192,7 +192,11 @@
     /**
      * Specifies if a user is disallowed from changing Wi-Fi
      * access points. The default value is <code>false</code>.
-     * <p>This restriction has no effect in a managed profile.
+     * <p>
+     * Device owner and profile owner can set this restriction, although the restriction has no
+     * effect in a managed profile. When it is set by the profile owner of an organization-owned
+     * managed profile on the parent profile, it will disallow the personal user from changing
+     * Wi-Fi access points.
      *
      * <p>Key for user restrictions.
      * <p>Type: Boolean
@@ -242,8 +246,13 @@
     /**
      * Specifies if a user is disallowed from turning on location sharing.
      * The default value is <code>false</code>.
-     * <p>In a managed profile, location sharing always reflects the primary user's setting, but
+     * <p>
+     * In a managed profile, location sharing always reflects the primary user's setting, but
      * can be overridden and forced off by setting this restriction to true in the managed profile.
+     * <p>
+     * Device owner and profile owner can set this restriction. When it is set by the profile
+     * owner of an organization-owned managed profile on the parent profile, it will prevent the
+     * user from turning on location sharing in the personal profile.
      *
      * <p>Key for user restrictions.
      * <p>Type: Boolean
@@ -349,9 +358,14 @@
      * Specifies if a user is disallowed from configuring bluetooth.
      * This does <em>not</em> restrict the user from turning bluetooth on or off.
      * The default value is <code>false</code>.
-     * <p>This restriction doesn't prevent the user from using bluetooth. For disallowing usage of
+     * <p>
+     * This restriction doesn't prevent the user from using bluetooth. For disallowing usage of
      * bluetooth completely on the device, use {@link #DISALLOW_BLUETOOTH}.
-     * <p>This restriction has no effect in a managed profile.
+     * <p>
+     * Device owner and profile owner can set this restriction, although the restriction has no
+     * effect in a managed profile. When it is set by the profile owner of an organization-owned
+     * managed profile on the parent profile, it will disallow the personal user from configuring
+     * bluetooth.
      *
      * <p>Key for user restrictions.
      * <p>Type: Boolean
@@ -364,8 +378,10 @@
     /**
      * Specifies if bluetooth is disallowed on the device.
      *
-     * <p> This restriction can only be set by the device owner and the profile owner on the
-     * primary user and it applies globally - i.e. it disables bluetooth on the entire device.
+     * <p> This restriction can only be set by the device owner, the profile owner on the
+     * primary user or the profile owner of an organization-owned managed profile on the
+     * parent profile and it applies globally - i.e. it disables bluetooth on the entire
+     * device.
      * <p>The default value is <code>false</code>.
      * <p>Key for user restrictions.
      * <p>Type: Boolean
@@ -377,8 +393,9 @@
 
     /**
      * Specifies if outgoing bluetooth sharing is disallowed on the device. Device owner and profile
-     * owner can set this restriction. When it is set by device owner, all users on this device will
-     * be affected.
+     * owner can set this restriction. When it is set by device owner or the profile owner of an
+     * organization-owned managed profile on the parent profile, all users on this device will be
+     * affected.
      *
      * <p>Default is <code>true</code> for managed profiles and false for otherwise. When a device
      * upgrades to {@link android.os.Build.VERSION_CODES#O}, the system sets it for all existing
@@ -394,7 +411,8 @@
 
     /**
      * Specifies if a user is disallowed from transferring files over
-     * USB. This can only be set by device owners and profile owners on the primary user.
+     * USB. This can only be set by device owners, profile owners on the primary user or
+     * profile owners of organization-owned managed profiles on the parent profile.
      * The default value is <code>false</code>.
      *
      * <p>Key for user restrictions.
@@ -453,8 +471,9 @@
 
     /**
      * Specifies if a user is disallowed from enabling or accessing debugging features. When set on
-     * the primary user, disables debugging features altogether, including USB debugging. When set
-     * on a managed profile or a secondary user, blocks debugging for that user only, including
+     * the primary user or by the profile owner of an organization-owned managed profile on the
+     * parent profile, disables debugging features altogether, including USB debugging. When set on
+     * a managed profile or a secondary user, blocks debugging for that user only, including
      * starting activities, making service calls, accessing content providers, sending broadcasts,
      * installing/uninstalling packages, clearing user data, etc.
      * The default value is <code>false</code>.
@@ -485,18 +504,19 @@
 
     /**
      * Specifies if a user is disallowed from enabling or disabling location providers. As a
-     * result, user is disallowed from turning on or off location. Device owner and profile owners
-     * can set this restriction and it only applies on the managed user.
+     * result, user is disallowed from turning on or off location.
      *
-     * <p>In a managed profile, location sharing is forced off when it's off on primary user, so
-     * user can still turn off location sharing on managed profile when the restriction is set by
-     * profile owner on managed profile.
-     *
-     * <p>This user restriction is different from {@link #DISALLOW_SHARE_LOCATION},
+     * <p>
+     * In a managed profile, location sharing is forced off when it is turned off on the primary
+     * user or by the profile owner of an organization-owned managed profile on the parent profile.
+     * The user can still turn off location sharing on a managed profile when the restriction is
+     * set by the profile owner on a managed profile.
+     * <p>
+     * This user restriction is different from {@link #DISALLOW_SHARE_LOCATION},
      * as the device owner or profile owner can still enable or disable location mode via
      * {@link DevicePolicyManager#setLocationEnabled} when this restriction is on.
-     *
-     * <p>The default value is <code>false</code>.
+     * <p>
+     * The default value is <code>false</code>.
      *
      * <p>Key for user restrictions.
      * <p>Type: Boolean
@@ -510,7 +530,8 @@
     /**
      * 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,
+     * <p>When restriction is set by device owners or profile owners of organization-owned
+     * managed profiles on the parent profile, 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>.
@@ -526,8 +547,9 @@
 
     /**
      * 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>.
+     * & portable hotspots. This can only be set by device owners, profile owners on the
+     * primary user or profile owners of organization-owned managed profiles on the parent profile.
+     * The default value is <code>false</code>.
      * <p>In Android 9.0 or higher, if tethering is enabled when this restriction is set,
      * tethering will be automatically turned off.
      *
@@ -571,8 +593,8 @@
 
     /**
      * Specifies if a user is disallowed from adding new users. This can only be set by device
-     * owners and profile owners on the primary user.
-     * The default value is <code>false</code>.
+     * owners, profile owners on the primary user or profile owners of organization-owned managed
+     * profiles on the parent profile. The default value is <code>false</code>.
      * <p>This restriction has no effect on secondary users and managed profiles since only the
      * primary user can add other users.
      *
@@ -621,7 +643,8 @@
 
     /**
      * Specifies if a user is disallowed from configuring cell
-     * broadcasts. This can only be set by device owners and profile owners on the primary user.
+     * broadcasts. This can only be set by device owners, profile owners on the primary user or
+     * profile owners of organization-owned managed profiles on the parent profile.
      * The default value is <code>false</code>.
      * <p>This restriction has no effect on secondary users and managed profiles since only the
      * primary user can configure cell broadcasts.
@@ -636,7 +659,8 @@
 
     /**
      * Specifies if a user is disallowed from configuring mobile
-     * networks. This can only be set by device owners and profile owners on the primary user.
+     * networks. This can only be set by device owners, profile owners on the primary user or
+     * profile owners of organization-owned managed profiles on the parent profile.
      * The default value is <code>false</code>.
      * <p>This restriction has no effect on secondary users and managed profiles since only the
      * primary user can configure mobile networks.
@@ -739,6 +763,10 @@
     /**
      * Specifies that the user is not allowed to send or receive
      * SMS messages. The default value is <code>false</code>.
+     * <p>
+     * Device owner and profile owner can set this restriction. When it is set by the
+     * profile owner of an organization-owned managed profile on the parent profile,
+     * it will disable SMS in the personal profile.
      *
      * <p>Key for user restrictions.
      * <p>Type: Boolean
@@ -857,7 +885,8 @@
 
     /**
      * Specifies if the user is not allowed to reboot the device into safe boot mode.
-     * This can only be set by device owners and profile owners on the primary user.
+     * This can only be set by device owners, profile owners on the primary user or profile
+     * owners of organization-owned managed profiles on the parent profile.
      * The default value is <code>false</code>.
      *
      * <p>Key for user restrictions.
@@ -896,6 +925,12 @@
 
     /**
      * Specifies if a user is not allowed to use the camera.
+     * <p>
+     * Device owner and profile owner can set this restriction. When the restriction is set by
+     * the device owner or the profile owner of an organization-owned managed profile on the
+     * parent profile, it is applied globally.
+     * <p>
+     * The default value is <code>false</code>.
      *
      * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
      * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
@@ -916,7 +951,8 @@
 
     /**
      * Specifies if a user is not allowed to use cellular data when roaming. This can only be set by
-     * device owners. The default value is <code>false</code>.
+     * device owners or profile owners of organization-owned managed profiles on the parent profile.
+     * The default value is <code>false</code>.
      *
      * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
      * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
@@ -1011,8 +1047,9 @@
      * Specifies if the contents of a user's screen is not allowed to be captured for artificial
      * intelligence purposes.
      *
-     * <p>Device owner and profile owner can set this restriction. When it is set by device owner,
-     * only the target user will be affected.
+     * <p>Device owner and profile owner can set this restriction. When it is set by the
+     * device owner or the profile owner of an organization-owned managed profile on the parent
+     * profile, only the target user will be affected.
      *
      * <p>The default value is <code>false</code>.
      *
@@ -1026,8 +1063,9 @@
      * Specifies if the current user is able to receive content suggestions for selections based on
      * the contents of their screen.
      *
-     * <p>Device owner and profile owner can set this restriction. When it is set by device owner,
-     * only the target user will be affected.
+     * <p>Device owner and profile owner can set this restriction. When it is set by the
+     * device owner or the profile owner of an organization-owned managed profile on the parent
+     * profile, only the target user will be affected.
      *
      * <p>The default value is <code>false</code>.
      *
@@ -1093,7 +1131,9 @@
      *
      * <p>The default value is <code>false</code>.
      *
-     * <p>This user restriction can only be applied by the Device Owner.
+     * <p>This user restriction can only be applied by the device owner or the profile owner
+     * of an organization-owned managed profile on the parent profile.
+     *
      * <p>Key for user restrictions.
      * <p>Type: Boolean
      * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
@@ -2413,6 +2453,13 @@
      * by {@link #createUser(String, String, int)} or {@link #createGuest(Context, String)}), it
      * takes less time.
      *
+     * <p>This method completes the majority of work necessary for user creation: it
+     * creates user data, CE and DE encryption keys, app data directories, initializes the user and
+     * grants default permissions. When pre-created users become "real" users, only then are
+     * components notified of new user creation by firing user creation broadcasts.
+     *
+     * <p>All pre-created users are removed during system upgrade.
+     *
      * <p>Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
      *
      * @param userType the type of user, such as {@link UserManager#USER_TYPE_FULL_GUEST}.
@@ -2424,6 +2471,7 @@
      *
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
     public @Nullable UserInfo preCreateUser(@NonNull String userType) {
         try {
             return mService.preCreateUser(userType);
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index ccbb0f1..ae75f3d 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -155,7 +155,7 @@
     /**
      * Configure an always-on haptics effect.
      *
-     * @param id The board-specific always-on ID to configure.
+     * @param alwaysOnId The board-specific always-on ID to configure.
      * @param effect Vibration effect to assign to always-on id. Passing null will disable it.
      * @param attributes {@link AudioAttributes} corresponding to the vibration. For example,
      *        specify {@link AudioAttributes#USAGE_ALARM} for alarm vibrations or
@@ -164,8 +164,17 @@
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)
-    public boolean setAlwaysOnEffect(int id, @Nullable VibrationEffect effect,
-                                  @Nullable AudioAttributes attributes) {
+    public boolean setAlwaysOnEffect(int alwaysOnId, @Nullable VibrationEffect effect,
+            @Nullable AudioAttributes attributes) {
+        return setAlwaysOnEffect(Process.myUid(), mPackageName, alwaysOnId, effect, attributes);
+    }
+
+    /**
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)
+    public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
+            @Nullable VibrationEffect effect, @Nullable AudioAttributes attributes) {
         Log.w(TAG, "Always-on effects aren't supported");
         return false;
     }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 089122d..e2b33e0 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6420,6 +6420,15 @@
                 "accessibility_button_target_component";
 
         /**
+         * The system class name of magnification controller which is a target to be toggled via
+         * accessibility shortcut or accessibility button.
+         *
+         * @hide
+         */
+        public static final String ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER =
+                "com.android.server.accessibility.MagnificationController";
+
+        /**
          * If touch exploration is enabled.
          */
         public static final String TOUCH_EXPLORATION_ENABLED = "touch_exploration_enabled";
diff --git a/core/java/android/timezone/CountryTimeZones.java b/core/java/android/timezone/CountryTimeZones.java
new file mode 100644
index 0000000..ada59d6
--- /dev/null
+++ b/core/java/android/timezone/CountryTimeZones.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.timezone;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.icu.util.TimeZone;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Information about a country's time zones.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public final class CountryTimeZones {
+
+    /**
+     * A mapping to a time zone ID with some associated metadata.
+     *
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static final class TimeZoneMapping {
+
+        private libcore.timezone.CountryTimeZones.TimeZoneMapping mDelegate;
+
+        TimeZoneMapping(libcore.timezone.CountryTimeZones.TimeZoneMapping delegate) {
+            this.mDelegate = Objects.requireNonNull(delegate);
+        }
+
+        /**
+         * Returns the ID for this mapping. See also {@link #getTimeZone()} which handles when the
+         * ID is unrecognized.
+         */
+        @NonNull
+        public String getTimeZoneId() {
+            return mDelegate.timeZoneId;
+        }
+
+        /**
+         * Returns a {@link TimeZone} object for this mapping, or {@code null} if the ID is
+         * unrecognized.
+         */
+        @Nullable
+        public TimeZone getTimeZone() {
+            return mDelegate.getTimeZone();
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            TimeZoneMapping that = (TimeZoneMapping) o;
+            return this.mDelegate.equals(that.mDelegate);
+        }
+
+        @Override
+        public int hashCode() {
+            return this.mDelegate.hashCode();
+        }
+
+        @Override
+        public String toString() {
+            return mDelegate.toString();
+        }
+    }
+
+    /**
+     * The result of lookup up a time zone using offset information (and possibly more).
+     *
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static final class OffsetResult {
+
+        private final TimeZone mTimeZone;
+        private final boolean mIsOnlyMatch;
+
+        /** Creates an instance with the supplied information. */
+        public OffsetResult(@NonNull TimeZone timeZone, boolean isOnlyMatch) {
+            mTimeZone = Objects.requireNonNull(timeZone);
+            mIsOnlyMatch = isOnlyMatch;
+        }
+
+        /**
+         * Returns a time zone that matches the supplied criteria.
+         */
+        @NonNull
+        public TimeZone getTimeZone() {
+            return mTimeZone;
+        }
+
+        /**
+         * Returns {@code true} if there is only one matching time zone for the supplied criteria.
+         */
+        public boolean isOnlyMatch() {
+            return mIsOnlyMatch;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            OffsetResult that = (OffsetResult) o;
+            return mIsOnlyMatch == that.mIsOnlyMatch
+                    && mTimeZone.getID().equals(that.mTimeZone.getID());
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mTimeZone, mIsOnlyMatch);
+        }
+
+        @Override
+        public String toString() {
+            return "OffsetResult{"
+                    + "mTimeZone=" + mTimeZone
+                    + ", mIsOnlyMatch=" + mIsOnlyMatch
+                    + '}';
+        }
+    }
+
+    @NonNull
+    private final libcore.timezone.CountryTimeZones mDelegate;
+
+    CountryTimeZones(libcore.timezone.CountryTimeZones delegate) {
+        mDelegate = delegate;
+    }
+
+    /**
+     * Returns true if the ISO code for the country is a match for the one specified.
+     */
+    public boolean isForCountryCode(@NonNull String countryIso) {
+        return mDelegate.isForCountryCode(countryIso);
+    }
+
+    /**
+     * Returns the default time zone ID for the country. Can return {@code null} in cases when no
+     * data is available or the time zone ID was not recognized.
+     */
+    @Nullable
+    public String getDefaultTimeZoneId() {
+        return mDelegate.getDefaultTimeZoneId();
+    }
+
+    /**
+     * Returns the default time zone for the country. Can return {@code null} in cases when no data
+     * is available or the time zone ID was not recognized.
+     */
+    @Nullable
+    public TimeZone getDefaultTimeZone() {
+        return mDelegate.getDefaultTimeZone();
+    }
+
+    /**
+     * Qualifier for a country's default time zone. {@code true} indicates whether the default
+     * would be a good choice <em>generally</em> when there's no other information available.
+     */
+    public boolean isDefaultTimeZoneBoosted() {
+        return mDelegate.getDefaultTimeZoneBoost();
+    }
+
+    /**
+     * Returns true if the country has at least one zone that is the same as UTC at the given time.
+     */
+    public boolean hasUtcZone(long whenMillis) {
+        return mDelegate.hasUtcZone(whenMillis);
+    }
+
+    /**
+     * Returns a time zone for the country, if there is one, that matches the desired properties. If
+     * there are multiple matches and the {@code bias} is one of them then it is returned, otherwise
+     * an arbitrary match is returned based on the {@link #getEffectiveTimeZoneMappingsAt(long)}
+     * ordering.
+     *
+     * @param totalOffsetMillis the offset from UTC at {@code whenMillis}
+     * @param isDst the Daylight Savings Time state at {@code whenMillis}. {@code true} means DST,
+     *     {@code false} means not DST, {@code null} means unknown
+     * @param dstOffsetMillis the part of {@code totalOffsetMillis} contributed by DST, only used if
+     *     {@code isDst} is {@code true}. The value can be {@code null} if the DST offset is
+     *     unknown
+     * @param whenMillis the UTC time to match against
+     * @param bias the time zone to prefer, can be {@code null}
+     */
+    @Nullable
+    public OffsetResult lookupByOffsetWithBias(int totalOffsetMillis, @Nullable Boolean isDst,
+            @SuppressLint("AutoBoxing") @Nullable Integer dstOffsetMillis, long whenMillis,
+            @Nullable TimeZone bias) {
+        libcore.timezone.CountryTimeZones.OffsetResult delegateOffsetResult =
+                mDelegate.lookupByOffsetWithBias(
+                        totalOffsetMillis, isDst, dstOffsetMillis, whenMillis, bias);
+        return delegateOffsetResult == null ? null :
+                new OffsetResult(delegateOffsetResult.mTimeZone, delegateOffsetResult.mOneMatch);
+    }
+
+    /**
+     * Returns an immutable, ordered list of time zone mappings for the country in an undefined but
+     * "priority" order, filtered so that only "effective" time zone IDs are returned. An
+     * "effective" time zone is one that differs from another time zone used in the country after
+     * {@code whenMillis}. The list can be empty if there were no zones configured or the configured
+     * zone IDs were not recognized.
+     */
+    @NonNull
+    public List<TimeZoneMapping> getEffectiveTimeZoneMappingsAt(long whenMillis) {
+        List<libcore.timezone.CountryTimeZones.TimeZoneMapping> delegateList =
+                mDelegate.getEffectiveTimeZoneMappingsAt(whenMillis);
+
+        List<TimeZoneMapping> toReturn = new ArrayList<>(delegateList.size());
+        for (libcore.timezone.CountryTimeZones.TimeZoneMapping delegateMapping : delegateList) {
+            toReturn.add(new TimeZoneMapping(delegateMapping));
+        }
+        return Collections.unmodifiableList(toReturn);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        CountryTimeZones that = (CountryTimeZones) o;
+        return mDelegate.equals(that.mDelegate);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mDelegate);
+    }
+
+    @Override
+    public String toString() {
+        return mDelegate.toString();
+    }
+}
diff --git a/core/java/android/timezone/TelephonyLookup.java b/core/java/android/timezone/TelephonyLookup.java
new file mode 100644
index 0000000..39dbe85
--- /dev/null
+++ b/core/java/android/timezone/TelephonyLookup.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.timezone;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Objects;
+
+/**
+ * A class that can find time zone-related information about telephony networks.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public class TelephonyLookup {
+
+    private static Object sLock = new Object();
+    @GuardedBy("sLock")
+    private static TelephonyLookup sInstance;
+
+    @NonNull
+    private final libcore.timezone.TelephonyLookup mDelegate;
+
+    /**
+     * Obtains an instance for use when resolving telephony time zone information. This method never
+     * returns {@code null}.
+     */
+    @NonNull
+    public static TelephonyLookup getInstance() {
+        synchronized (sLock) {
+            if (sInstance == null) {
+                sInstance = new TelephonyLookup(libcore.timezone.TelephonyLookup.getInstance());
+            }
+            return sInstance;
+        }
+    }
+
+    private TelephonyLookup(@NonNull libcore.timezone.TelephonyLookup delegate) {
+        mDelegate = Objects.requireNonNull(delegate);
+    }
+
+    /**
+     * Returns an object capable of querying telephony network information. This method can return
+     * {@code null} in the event of an error while reading the underlying data files.
+     */
+    @Nullable
+    public TelephonyNetworkFinder getTelephonyNetworkFinder() {
+        libcore.timezone.TelephonyNetworkFinder telephonyNetworkFinderDelegate =
+                mDelegate.getTelephonyNetworkFinder();
+        return telephonyNetworkFinderDelegate != null
+                ? new TelephonyNetworkFinder(telephonyNetworkFinderDelegate) : null;
+    }
+}
diff --git a/core/java/android/timezone/TelephonyNetwork.java b/core/java/android/timezone/TelephonyNetwork.java
new file mode 100644
index 0000000..ae39fbd
--- /dev/null
+++ b/core/java/android/timezone/TelephonyNetwork.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.timezone;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import java.util.Objects;
+
+/**
+ * Information about a telephony network.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public class TelephonyNetwork {
+
+    @NonNull
+    private final libcore.timezone.TelephonyNetwork mDelegate;
+
+    TelephonyNetwork(@NonNull libcore.timezone.TelephonyNetwork delegate) {
+        mDelegate = Objects.requireNonNull(delegate);
+    }
+
+    /**
+     * Returns the Mobile Country Code of the network.
+     */
+    @NonNull
+    public String getMcc() {
+        return mDelegate.getMcc();
+    }
+
+    /**
+     * Returns the Mobile Network Code of the network.
+     */
+    @NonNull
+    public String getMnc() {
+        return mDelegate.getMnc();
+    }
+
+    /**
+     * Returns the country in which the network operates as an ISO 3166 alpha-2 (lower case).
+     */
+    @NonNull
+    public String getCountryIsoCode() {
+        return mDelegate.getCountryIsoCode();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        TelephonyNetwork that = (TelephonyNetwork) o;
+        return mDelegate.equals(that.mDelegate);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mDelegate);
+    }
+
+    @Override
+    public String toString() {
+        return "TelephonyNetwork{"
+                + "mDelegate=" + mDelegate
+                + '}';
+    }
+}
diff --git a/core/java/android/timezone/TelephonyNetworkFinder.java b/core/java/android/timezone/TelephonyNetworkFinder.java
new file mode 100644
index 0000000..a81a516
--- /dev/null
+++ b/core/java/android/timezone/TelephonyNetworkFinder.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.timezone;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+import java.util.Objects;
+
+/**
+ * A class that can find telephony networks loaded via {@link TelephonyLookup}.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public class TelephonyNetworkFinder {
+
+    @NonNull
+    private final libcore.timezone.TelephonyNetworkFinder mDelegate;
+
+    TelephonyNetworkFinder(libcore.timezone.TelephonyNetworkFinder delegate) {
+        mDelegate = Objects.requireNonNull(delegate);
+    }
+
+    /**
+     * Returns information held about a specific MCC + MNC combination. It is expected for this
+     * method to return {@code null}. Only known, unusual networks will typically have information
+     * returned, e.g. if they operate in countries other than the one suggested by their MCC.
+     */
+    @Nullable
+    public TelephonyNetwork findNetworkByMccMnc(@NonNull String mcc, @NonNull String mnc) {
+        Objects.requireNonNull(mcc);
+        Objects.requireNonNull(mnc);
+
+        libcore.timezone.TelephonyNetwork telephonyNetworkDelegate =
+                mDelegate.findNetworkByMccMnc(mcc, mnc);
+        return telephonyNetworkDelegate != null
+                ? new TelephonyNetwork(telephonyNetworkDelegate) : null;
+    }
+}
diff --git a/core/java/android/timezone/TimeZoneFinder.java b/core/java/android/timezone/TimeZoneFinder.java
new file mode 100644
index 0000000..15dfe62
--- /dev/null
+++ b/core/java/android/timezone/TimeZoneFinder.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.timezone;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * A class that can be used to find time zones.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public final class TimeZoneFinder {
+
+    private static Object sLock = new Object();
+    @GuardedBy("sLock")
+    private static TimeZoneFinder sInstance;
+
+    private final libcore.timezone.TimeZoneFinder mDelegate;
+
+    private TimeZoneFinder(libcore.timezone.TimeZoneFinder delegate) {
+        mDelegate = delegate;
+    }
+
+    /**
+     * Obtains an instance for use when resolving telephony time zone information. This method never
+     * returns {@code null}.
+     */
+    @NonNull
+    public static TimeZoneFinder getInstance() {
+        synchronized (sLock) {
+            if (sInstance == null) {
+                sInstance = new TimeZoneFinder(libcore.timezone.TimeZoneFinder.getInstance());
+            }
+        }
+        return sInstance;
+    }
+
+    /**
+     * Returns a {@link CountryTimeZones} object associated with the specified country code.
+     * Caching is handled as needed. If the country code is not recognized or there is an error
+     * during lookup this method can return null.
+     */
+    @Nullable
+    public CountryTimeZones lookupCountryTimeZones(@NonNull String countryIso) {
+        libcore.timezone.CountryTimeZones delegate = mDelegate.lookupCountryTimeZones(countryIso);
+        return delegate == null ? null : new CountryTimeZones(delegate);
+    }
+}
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 4368115..178b3c0 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.CONFIGURE_DISPLAY_COLOR_MODE;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
@@ -1010,6 +1011,9 @@
      * @return Supported WCG color spaces.
      * @hide
      */
+    @SuppressLint("VisiblySynchronized")
+    @NonNull
+    @TestApi
     public @ColorMode ColorSpace[] getSupportedWideColorGamut() {
         synchronized (this) {
             final ColorSpace[] defaultColorSpaces = new ColorSpace[0];
diff --git a/core/java/android/view/InputEventReceiver.java b/core/java/android/view/InputEventReceiver.java
index c67ff6e..7986ceb 100644
--- a/core/java/android/view/InputEventReceiver.java
+++ b/core/java/android/view/InputEventReceiver.java
@@ -128,6 +128,19 @@
     }
 
     /**
+     * Called when a focus event is received.
+     *
+     * @param hasFocus if true, the window associated with this input channel has just received
+     *                 focus
+     *                 if false, the window associated with this input channel has just lost focus
+     * @param inTouchMode if true, the device is in touch mode
+     *                    if false, the device is not in touch mode
+     */
+    // Called from native code.
+    public void onFocusEvent(boolean hasFocus, boolean inTouchMode) {
+    }
+
+    /**
      * Called when a batched input event is pending.
      *
      * The batched input event will continue to accumulate additional movement
@@ -213,8 +226,13 @@
         onBatchedInputEventPending();
     }
 
-    public static interface Factory {
-        public InputEventReceiver createInputEventReceiver(
-                InputChannel inputChannel, Looper looper);
+    /**
+     * Factory for InputEventReceiver
+     */
+    public interface Factory {
+        /**
+         * Create a new InputReceiver for a given inputChannel
+         */
+        InputEventReceiver createInputEventReceiver(InputChannel inputChannel, Looper looper);
     }
 }
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 1782544..0de1a4f 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -42,6 +42,7 @@
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.SurfaceControl.Transaction;
+import android.view.accessibility.AccessibilityNodeInfo;
 
 import com.android.internal.view.SurfaceCallbackHelper;
 
@@ -201,6 +202,9 @@
     private SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
     private int mParentSurfaceGenerationId;
 
+    // The token of embedded windowless view hierarchy.
+    private IBinder mEmbeddedViewHierarchy;
+
     public SurfaceView(Context context) {
         this(context, null);
     }
@@ -1531,4 +1535,27 @@
         if (viewRoot == null) return;
         viewRoot.setUseBLASTSyncTransaction();
     }
+
+    /**
+     * Add the token of embedded view hierarchy. Set {@code null} to clear the embedded view
+     * hierarchy.
+     *
+     * @param token IBinder token.
+     * @hide
+     */
+    public void setEmbeddedViewHierarchy(IBinder token) {
+        mEmbeddedViewHierarchy = token;
+    }
+
+    /** @hide */
+    @Override
+    public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfoInternal(info);
+        if (mEmbeddedViewHierarchy == null) {
+            return;
+        }
+        // Add a leashed child when this SurfaceView embeds another view hierarchy. Getting this
+        // leashed child would return the root node in the embedded hierarchy
+        info.addChild(mEmbeddedViewHierarchy);
+    }
 }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e0f6e06..2ef944f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -8044,6 +8044,11 @@
         }
 
         @Override
+        public void onFocusEvent(boolean hasFocus, boolean inTouchMode) {
+            windowFocusChanged(hasFocus, inTouchMode);
+        }
+
+        @Override
         public void dispose() {
             unscheduleConsumeBatchedInput();
             super.dispose();
diff --git a/core/java/android/view/WindowContainerTransaction.java b/core/java/android/view/WindowContainerTransaction.java
index 253794f..c55caa3 100644
--- a/core/java/android/view/WindowContainerTransaction.java
+++ b/core/java/android/view/WindowContainerTransaction.java
@@ -74,6 +74,18 @@
         return this;
     }
 
+    /**
+     * Sets whether a container or any of its children can be focusable. When {@code false}, no
+     * child can be focused; however, when {@code true}, it is still possible for children to be
+     * non-focusable due to WM policy.
+     */
+    public WindowContainerTransaction setFocusable(IWindowContainer container, boolean focusable) {
+        Change chg = getOrCreateChange(container.asBinder());
+        chg.mFocusable = focusable;
+        chg.mChangeMask |= Change.CHANGE_FOCUSABLE;
+        return this;
+    }
+
     public Map<IBinder, Change> getChanges() {
         return mChanges;
     }
@@ -112,7 +124,11 @@
      * @hide
      */
     public static class Change implements Parcelable {
+        public static final int CHANGE_FOCUSABLE = 1;
+
         private final Configuration mConfiguration = new Configuration();
+        private boolean mFocusable = true;
+        private int mChangeMask = 0;
         private @ActivityInfo.Config int mConfigSetMask = 0;
         private @WindowConfiguration.WindowConfig int mWindowSetMask = 0;
 
@@ -123,6 +139,8 @@
 
         protected Change(Parcel in) {
             mConfiguration.readFromParcel(in);
+            mFocusable = in.readBoolean();
+            mChangeMask = in.readInt();
             mConfigSetMask = in.readInt();
             mWindowSetMask = in.readInt();
             mSchedulePipCallback = (in.readInt() != 0);
@@ -136,6 +154,18 @@
             return mConfiguration;
         }
 
+        /** Gets the requested focusable value */
+        public boolean getFocusable() {
+            if ((mChangeMask & CHANGE_FOCUSABLE) == 0) {
+                throw new RuntimeException("Focusable not set. check CHANGE_FOCUSABLE first");
+            }
+            return mFocusable;
+        }
+
+        public int getChangeMask() {
+            return mChangeMask;
+        }
+
         @ActivityInfo.Config
         public int getConfigSetMask() {
             return mConfigSetMask;
@@ -170,6 +200,9 @@
             if (changesSss) {
                 sb.append("ssw:" + mConfiguration.smallestScreenWidthDp + ",");
             }
+            if ((mChangeMask & CHANGE_FOCUSABLE) != 0) {
+                sb.append("focusable:" + mFocusable + ",");
+            }
             sb.append("}");
             return sb.toString();
         }
@@ -177,6 +210,8 @@
         @Override
         public void writeToParcel(Parcel dest, int flags) {
             mConfiguration.writeToParcel(dest, flags);
+            dest.writeBoolean(mFocusable);
+            dest.writeInt(mChangeMask);
             dest.writeInt(mConfigSetMask);
             dest.writeInt(mWindowSetMask);
 
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 914ff18..b9f08ad 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -17,10 +17,14 @@
 package android.view.accessibility;
 
 import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.graphics.Bitmap;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.IBinder;
 import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
@@ -337,6 +341,48 @@
         return emptyWindows;
     }
 
+
+    /**
+     * Finds an {@link AccessibilityNodeInfo} by accessibility id and given leash token instead of
+     * window id. This method is used to find the leashed node on the embedded view hierarchy.
+     *
+     * @param connectionId The id of a connection for interacting with the system.
+     * @param leashToken The token of the embedded hierarchy.
+     * @param accessibilityNodeId A unique view id or virtual descendant id from
+     *     where to start the search. Use
+     *     {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
+     *     to start from the root.
+     * @param bypassCache Whether to bypass the cache while looking for the node.
+     * @param prefetchFlags flags to guide prefetching.
+     * @param arguments Optional action arguments.
+     * @return An {@link AccessibilityNodeInfo} if found, null otherwise.
+     */
+    public @Nullable AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(
+            int connectionId, @NonNull IBinder leashToken, long accessibilityNodeId,
+            boolean bypassCache, int prefetchFlags, Bundle arguments) {
+        if (leashToken == null) {
+            return null;
+        }
+        int windowId = -1;
+        try {
+            IAccessibilityServiceConnection connection = getConnection(connectionId);
+            if (connection != null) {
+                windowId = connection.getWindowIdForLeashToken(leashToken);
+            } else {
+                if (DEBUG) {
+                    Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
+                }
+            }
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error while calling remote getWindowIdForLeashToken", re);
+        }
+        if (windowId == -1) {
+            return null;
+        }
+        return findAccessibilityNodeInfoByAccessibilityId(connectionId, windowId,
+                accessibilityNodeId, bypassCache, prefetchFlags, arguments);
+    }
+
     /**
      * Finds an {@link AccessibilityNodeInfo} by accessibility id.
      *
@@ -783,6 +829,31 @@
     }
 
     /**
+     * Takes the screenshot of the specified display and returns it by bitmap format.
+     *
+     * @param connectionId The id of a connection for interacting with the system.
+     * @param displayId The logic display id, use {@link Display#DEFAULT_DISPLAY} for
+     *                  default display.
+     * @return The screenshot bitmap on success, null otherwise.
+     */
+    public Bitmap takeScreenshot(int connectionId, int displayId) {
+        Bitmap screenShot = null;
+        try {
+            IAccessibilityServiceConnection connection = getConnection(connectionId);
+            if (connection != null) {
+                screenShot = connection.takeScreenshot(displayId);
+            } else {
+                if (DEBUG) {
+                    Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
+                }
+            }
+        } catch (RemoteException re) {
+            Log.w(LOG_TAG, "Error while calling remote takeScreenshot", re);
+        }
+        return screenShot;
+    }
+
+    /**
      * Clears the result state.
      */
     private void clearResultLocked() {
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 2e5a4b5..3dfeffb 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -1195,6 +1195,19 @@
     @TestApi
     @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
     public void performAccessibilityShortcut() {
+        performAccessibilityShortcut(null);
+    }
+
+    /**
+     * Perform the accessibility shortcut for the given target which is assigned to the shortcut.
+     *
+     * @param targetName The flattened {@link ComponentName} string or the class name of a system
+     *        class implementing a supported accessibility feature, or {@code null} if there's no
+     *        specified target.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
+    public void performAccessibilityShortcut(@Nullable String targetName) {
         final IAccessibilityManager service;
         synchronized (mLock) {
             service = getServiceLocked();
@@ -1203,7 +1216,7 @@
             }
         }
         try {
-            service.performAccessibilityShortcut();
+            service.performAccessibilityShortcut(targetName);
         } catch (RemoteException re) {
             Log.e(LOG_TAG, "Error performing accessibility shortcut. ", re);
         }
@@ -1270,7 +1283,22 @@
      * @param displayId The logical display id.
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
     public void notifyAccessibilityButtonClicked(int displayId) {
+        notifyAccessibilityButtonClicked(displayId, null);
+    }
+
+    /**
+     * Perform the accessibility button for the given target which is assigned to the button.
+     *
+     * @param displayId displayId The logical display id.
+     * @param targetName The flattened {@link ComponentName} string or the class name of a system
+     *        class implementing a supported accessibility feature, or {@code null} if there's no
+     *        specified target.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
+    public void notifyAccessibilityButtonClicked(int displayId, @Nullable String targetName) {
         final IAccessibilityManager service;
         synchronized (mLock) {
             service = getServiceLocked();
@@ -1279,7 +1307,7 @@
             }
         }
         try {
-            service.notifyAccessibilityButtonClicked(displayId);
+            service.notifyAccessibilityButtonClicked(displayId, targetName);
         } catch (RemoteException re) {
             Log.e(LOG_TAG, "Error while dispatching accessibility button click", re);
         }
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 92aa7d5..184f3302 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -31,6 +31,7 @@
 import android.graphics.Region;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.InputType;
@@ -110,6 +111,9 @@
     public static final int ROOT_ITEM_ID = Integer.MAX_VALUE - 1;
 
     /** @hide */
+    public static final int LEASHED_ITEM_ID = Integer.MAX_VALUE - 2;
+
+    /** @hide */
     public static final long UNDEFINED_NODE_ID = makeNodeId(UNDEFINED_ITEM_ID, UNDEFINED_ITEM_ID);
 
     /** @hide */
@@ -117,6 +121,10 @@
             AccessibilityNodeProvider.HOST_VIEW_ID);
 
     /** @hide */
+    public static final long LEASHED_NODE_ID = makeNodeId(LEASHED_ITEM_ID,
+            AccessibilityNodeProvider.HOST_VIEW_ID);
+
+    /** @hide */
     public static final int FLAG_PREFETCH_PREDECESSORS = 0x00000001;
 
     /** @hide */
@@ -788,6 +796,10 @@
 
     private TouchDelegateInfo mTouchDelegateInfo;
 
+    private IBinder mLeashedChild;
+    private IBinder mLeashedParent;
+    private long mLeashedParentNodeId = UNDEFINED_NODE_ID;
+
     /**
      * Creates a new {@link AccessibilityNodeInfo}.
      */
@@ -1039,7 +1051,12 @@
             return null;
         }
         final long childId = mChildNodeIds.get(index);
-        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+        final AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+        if (mLeashedChild != null && childId == LEASHED_NODE_ID) {
+            return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mLeashedChild,
+                    ROOT_NODE_ID, false, FLAG_PREFETCH_DESCENDANTS, null);
+        }
+
         return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mWindowId,
                 childId, false, FLAG_PREFETCH_DESCENDANTS, null);
     }
@@ -1062,6 +1079,43 @@
     }
 
     /**
+     * Adds a view root from leashed content as a child. This method is used to embedded another
+     * view hierarchy.
+     * <p>
+     * <strong>Note:</strong> Only one leashed child is permitted.
+     * </p>
+     * <p>
+     * <strong>Note:</strong> Cannot be called from an
+     * {@link android.accessibilityservice.AccessibilityService}.
+     * This class is made immutable before being delivered to an AccessibilityService.
+     * Note that a view cannot be made its own child.
+     * </p>
+     *
+     * @param token The token to which a view root is added.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     * @hide
+     */
+    @TestApi
+    public void addChild(@NonNull IBinder token) {
+        enforceNotSealed();
+        if (token == null) {
+            return;
+        }
+        if (mChildNodeIds == null) {
+            mChildNodeIds = new LongArray();
+        }
+
+        mLeashedChild = token;
+        // Checking uniqueness.
+        // Since only one leashed child is permitted, skip adding ID if the ID already exists.
+        if (mChildNodeIds.indexOf(LEASHED_NODE_ID) >= 0) {
+            return;
+        }
+        mChildNodeIds.add(LEASHED_NODE_ID);
+    }
+
+    /**
      * Unchecked version of {@link #addChild(View)} that does not verify
      * uniqueness. For framework use only.
      *
@@ -1090,6 +1144,38 @@
     }
 
     /**
+     * Removes a leashed child. If the child was not previously added to the node,
+     * calling this method has no effect.
+     * <p>
+     * <strong>Note:</strong> Cannot be called from an
+     * {@link android.accessibilityservice.AccessibilityService}.
+     * This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param token The token of the leashed child
+     * @return true if the child was present
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     * @hide
+     */
+    public boolean removeChild(IBinder token) {
+        enforceNotSealed();
+        if (mChildNodeIds == null || mLeashedChild == null) {
+            return false;
+        }
+        if (!mLeashedChild.equals(token)) {
+            return false;
+        }
+        final int index = mChildNodeIds.indexOf(LEASHED_NODE_ID);
+        mLeashedChild = null;
+        if (index < 0) {
+            return false;
+        }
+        mChildNodeIds.remove(index);
+        return true;
+    }
+
+    /**
      * Adds a virtual child which is a descendant of the given <code>root</code>.
      * If <code>virtualDescendantId</code> is {@link View#NO_ID} the root
      * is added as a child.
@@ -1668,6 +1754,9 @@
      */
     public AccessibilityNodeInfo getParent() {
         enforceSealed();
+        if (mLeashedParent != null && mLeashedParentNodeId != UNDEFINED_NODE_ID) {
+            return getNodeForAccessibilityId(mConnectionId, mLeashedParent, mLeashedParentNodeId);
+        }
         return getNodeForAccessibilityId(mConnectionId, mWindowId, mParentNodeId);
     }
 
@@ -3257,6 +3346,40 @@
     }
 
     /**
+     * Sets the token and node id of the leashed parent.
+     *
+     * @param token The token.
+     * @param viewId The accessibility view id.
+     * @hide
+     */
+    @TestApi
+    public void setLeashedParent(@Nullable IBinder token, int viewId) {
+        enforceNotSealed();
+        mLeashedParent = token;
+        mLeashedParentNodeId = makeNodeId(viewId, AccessibilityNodeProvider.HOST_VIEW_ID);
+    }
+
+    /**
+     * Gets the token of the leashed parent.
+     *
+     * @return The token.
+     * @hide
+     */
+    public @Nullable IBinder getLeashedParent() {
+        return mLeashedParent;
+    }
+
+    /**
+     * Gets the node id of the leashed parent.
+     *
+     * @return The accessibility node id.
+     * @hide
+     */
+    public long getLeashedParentNodeId() {
+        return mLeashedParentNodeId;
+    }
+
+    /**
      * Sets if this instance is sealed.
      *
      * @param sealed Whether is sealed.
@@ -3559,6 +3682,18 @@
         if (!Objects.equals(mTouchDelegateInfo, DEFAULT.mTouchDelegateInfo)) {
             nonDefaultFields |= bitAt(fieldIndex);
         }
+        fieldIndex++;
+        if (mLeashedChild != DEFAULT.mLeashedChild) {
+            nonDefaultFields |= bitAt(fieldIndex);
+        }
+        fieldIndex++;
+        if (mLeashedParent != DEFAULT.mLeashedParent) {
+            nonDefaultFields |= bitAt(fieldIndex);
+        }
+        fieldIndex++;
+        if (mLeashedParentNodeId != DEFAULT.mLeashedParentNodeId) {
+            nonDefaultFields |= bitAt(fieldIndex);
+        }
         int totalFields = fieldIndex;
         parcel.writeLong(nonDefaultFields);
 
@@ -3685,6 +3820,16 @@
             mTouchDelegateInfo.writeToParcel(parcel, flags);
         }
 
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            parcel.writeStrongBinder(mLeashedChild);
+        }
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            parcel.writeStrongBinder(mLeashedParent);
+        }
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            parcel.writeLong(mLeashedParentNodeId);
+        }
+
         if (DEBUG) {
             fieldIndex--;
             if (totalFields != fieldIndex) {
@@ -3768,6 +3913,10 @@
         final TouchDelegateInfo otherInfo = other.mTouchDelegateInfo;
         mTouchDelegateInfo = (otherInfo != null)
                 ? new TouchDelegateInfo(otherInfo.mTargetMap, true) : null;
+
+        mLeashedChild = other.mLeashedChild;
+        mLeashedParent = other.mLeashedParent;
+        mLeashedParentNodeId = other.mLeashedParentNodeId;
     }
 
     private void initPoolingInfos(AccessibilityNodeInfo other) {
@@ -3921,6 +4070,16 @@
             mTouchDelegateInfo = TouchDelegateInfo.CREATOR.createFromParcel(parcel);
         }
 
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            mLeashedChild = parcel.readStrongBinder();
+        }
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            mLeashedParent = parcel.readStrongBinder();
+        }
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            mLeashedParentNodeId = parcel.readLong();
+        }
+
         mSealed = sealed;
     }
 
@@ -4200,6 +4359,19 @@
                         | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS, null);
     }
 
+    private static AccessibilityNodeInfo getNodeForAccessibilityId(int connectionId,
+            IBinder leashToken, long accessibilityId) {
+        if (!((leashToken != null)
+                && (getAccessibilityViewId(accessibilityId) != UNDEFINED_ITEM_ID)
+                && (connectionId != UNDEFINED_CONNECTION_ID))) {
+            return null;
+        }
+        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+        return client.findAccessibilityNodeInfoByAccessibilityId(connectionId,
+                leashToken, accessibilityId, false, FLAG_PREFETCH_PREDECESSORS
+                        | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS, null);
+    }
+
     /** @hide */
     public static String idToString(long accessibilityId) {
         int accessibilityViewId = getAccessibilityViewId(accessibilityId);
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 392db57..fcaaa2e 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -66,12 +66,12 @@
     // Used by UiAutomation
     IBinder getWindowToken(int windowId, int userId);
 
-    void notifyAccessibilityButtonClicked(int displayId);
+    void notifyAccessibilityButtonClicked(int displayId, String targetName);
 
     void notifyAccessibilityButtonVisibilityChanged(boolean available);
 
     // Requires Manifest.permission.MANAGE_ACCESSIBILITY
-    void performAccessibilityShortcut();
+    void performAccessibilityShortcut(String targetName);
 
     // Requires Manifest.permission.MANAGE_ACCESSIBILITY
     List<String> getAccessibilityShortcutTargets(int shortcutType);
diff --git a/core/java/android/webkit/PacProcessor.java b/core/java/android/webkit/PacProcessor.java
new file mode 100644
index 0000000..fe8bbb8
--- /dev/null
+++ b/core/java/android/webkit/PacProcessor.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.webkit;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+
+/**
+ * Class to evaluate PAC scripts.
+ * @hide
+ */
+
+@SystemApi
+public interface PacProcessor {
+
+    /**
+     * Returns the default PacProcessor instance.
+     *
+     * @return the default PacProcessor instance.
+     */
+    @NonNull
+    static PacProcessor getInstance() {
+        return WebViewFactory.getProvider().getPacProcessor();
+    }
+
+    /**
+     * Set PAC script to use.
+     *
+     * @param script PAC script.
+     * @return true if PAC script is successfully set.
+     */
+    boolean setProxyScript(@NonNull String script);
+
+    /**
+     * Gets a list of proxy servers to use.
+     * @param url The URL being accessed.
+     * @return a PAC-style semicolon-separated list of valid proxy servers.
+     *         For example: "PROXY xxx.xxx.xxx.xxx:xx; SOCKS yyy.yyy.yyy:yy".
+     */
+    @Nullable
+    String makeProxyRequest(@NonNull String url);
+}
diff --git a/core/java/android/webkit/WebViewFactoryProvider.java b/core/java/android/webkit/WebViewFactoryProvider.java
index 6a1ed39..f7c3ec0 100644
--- a/core/java/android/webkit/WebViewFactoryProvider.java
+++ b/core/java/android/webkit/WebViewFactoryProvider.java
@@ -175,6 +175,15 @@
     WebViewDatabase getWebViewDatabase(Context context);
 
     /**
+     * Gets the singleton PacProcessor instance.
+     * @return the PacProcessor instance
+     */
+    @NonNull
+    default PacProcessor getPacProcessor() {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    /**
      * Gets the classloader used to load internal WebView implementation classes. This interface
      * should only be used by the WebView Support Library.
      */
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index f851e10..b891af5 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -152,7 +152,7 @@
 
     // Specifies whether to allow starting a cursor drag by dragging anywhere over the text.
     @VisibleForTesting
-    public static boolean FLAG_ENABLE_CURSOR_DRAG = false;
+    public static boolean FLAG_ENABLE_CURSOR_DRAG = true;
     // Specifies whether to use the magnifier when pressing the insertion or selection handles.
     private static final boolean FLAG_USE_MAGNIFIER = true;
 
@@ -5741,10 +5741,10 @@
                 return;
             }
             switch (event.getActionMasked()) {
-                case MotionEvent.ACTION_DOWN:
-                    mIsDraggingCursor = false;
-                    break;
                 case MotionEvent.ACTION_MOVE:
+                    if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
+                        break;
+                    }
                     if (mIsDraggingCursor) {
                         performCursorDrag(event);
                     } else if (FLAG_ENABLE_CURSOR_DRAG
diff --git a/core/java/android/widget/EditorTouchState.java b/core/java/android/widget/EditorTouchState.java
index 6277afe..b13ca42 100644
--- a/core/java/android/widget/EditorTouchState.java
+++ b/core/java/android/widget/EditorTouchState.java
@@ -173,6 +173,13 @@
                     mIsDragCloseToVertical = (4 * deltaXSquared) <= distanceSquared;
                 }
             }
+        } else if (action == MotionEvent.ACTION_CANCEL) {
+            mLastDownMillis = 0;
+            mLastUpMillis = 0;
+            mMultiTapStatus = MultiTapStatus.NONE;
+            mMultiTapInSameArea = false;
+            mMovedEnoughForDrag = false;
+            mIsDragCloseToVertical = false;
         }
     }
 
diff --git a/core/java/android/widget/TextClock.java b/core/java/android/widget/TextClock.java
index 5731e50..8565493 100644
--- a/core/java/android/widget/TextClock.java
+++ b/core/java/android/widget/TextClock.java
@@ -422,7 +422,7 @@
     /**
      * Update the displayed time if necessary and invalidate the view.
      */
-    public void refresh() {
+    public void refreshTime() {
         onTimeChanged();
         invalidate();
     }
diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java
index 9bdb4c1..d119b2e 100644
--- a/core/java/android/widget/Toast.java
+++ b/core/java/android/widget/Toast.java
@@ -16,6 +16,8 @@
 
 package android.widget;
 
+import static com.android.internal.util.Preconditions.checkNotNull;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -42,8 +44,12 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * A toast is a view containing a quick little message for the user.  The toast class
@@ -262,6 +268,29 @@
     }
 
     /**
+     * Adds a callback to be notified when the toast is shown or hidden.
+     *
+     * Note that if the toast is blocked for some reason you won't get a call back.
+     *
+     * @see #removeCallback(Callback)
+     */
+    public void addCallback(@NonNull Callback callback) {
+        checkNotNull(callback);
+        synchronized (mTN.mCallbacks) {
+            mTN.mCallbacks.add(callback);
+        }
+    }
+
+    /**
+     * Removes a callback previously added with {@link #addCallback(Callback)}.
+     */
+    public void removeCallback(@NonNull Callback callback) {
+        synchronized (mTN.mCallbacks) {
+            mTN.mCallbacks.remove(callback);
+        }
+    }
+
+    /**
      * Gets the LayoutParams for the Toast window.
      * @hide
      */
@@ -389,6 +418,9 @@
 
         String mPackageName;
 
+        @GuardedBy("mCallbacks")
+        private final List<Callback> mCallbacks = new ArrayList<>();
+
         static final long SHORT_DURATION_TIMEOUT = 4000;
         static final long LONG_DURATION_TIMEOUT = 7000;
 
@@ -449,6 +481,12 @@
             };
         }
 
+        private List<Callback> getCallbacks() {
+            synchronized (mCallbacks) {
+                return new ArrayList<>(mCallbacks);
+            }
+        }
+
         /**
          * schedule handleShow into the right thread
          */
@@ -522,6 +560,9 @@
                 try {
                     mWM.addView(mView, mParams);
                     trySendAccessibilityEvent();
+                    for (Callback callback : getCallbacks()) {
+                        callback.onToastShown();
+                    }
                 } catch (WindowManager.BadTokenException e) {
                     /* ignore */
                 }
@@ -564,8 +605,30 @@
                 } catch (RemoteException e) {
                 }
 
+                for (Callback callback : getCallbacks()) {
+                    callback.onToastHidden();
+                }
                 mView = null;
             }
         }
     }
+
+    /**
+     * Callback object to be called when the toast is shown or hidden.
+     *
+     * Callback methods will be called on the looper thread provided on construction.
+     *
+     * @see #addCallback(Callback)
+     */
+    public abstract static class Callback {
+        /**
+         * Called when the toast is displayed on the screen.
+         */
+        public void onToastShown() {}
+
+        /**
+         * Called when the toast is hidden.
+         */
+        public void onToastHidden() {}
+    }
 }
diff --git a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
index de204ba..a9f0175 100644
--- a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
+++ b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
@@ -19,6 +19,15 @@
 import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
 import static android.view.accessibility.AccessibilityManager.ShortcutType;
 
+import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
+import static com.android.internal.app.AccessibilityButtonChooserActivity.WhiteListingFeatureElementIndex.COMPONENT_ID;
+import static com.android.internal.app.AccessibilityButtonChooserActivity.WhiteListingFeatureElementIndex.FRAGMENT_TYPE;
+import static com.android.internal.app.AccessibilityButtonChooserActivity.WhiteListingFeatureElementIndex.ICON_ID;
+import static com.android.internal.app.AccessibilityButtonChooserActivity.WhiteListingFeatureElementIndex.LABEL_ID;
+import static com.android.internal.app.AccessibilityButtonChooserActivity.WhiteListingFeatureElementIndex.SETTINGS_KEY;
+
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -63,10 +72,6 @@
  * Activity used to display and persist a service or feature target for the Accessibility button.
  */
 public class AccessibilityButtonChooserActivity extends Activity {
-
-    private static final String MAGNIFICATION_COMPONENT_ID =
-            "com.android.server.accessibility.MagnificationController";
-
     private static final char SERVICES_SEPARATOR = ':';
     private static final TextUtils.SimpleStringSplitter sStringColonSplitter =
             new TextUtils.SimpleStringSplitter(SERVICES_SEPARATOR);
@@ -76,7 +81,7 @@
             ACCESSIBILITY_SHORTCUT_KEY);
 
     private int mShortcutType;
-    private List<AccessibilityButtonTarget> mTargets = new ArrayList<>();
+    private final List<AccessibilityButtonTarget> mTargets = new ArrayList<>();
     private AlertDialog mAlertDialog;
     private TargetAdapter mTargetAdapter;
 
@@ -99,7 +104,7 @@
             UserShortcutType.TRIPLETAP,
     })
     /** Denotes the user shortcut type. */
-    public @interface UserShortcutType {
+    private @interface UserShortcutType {
         int DEFAULT = 0;
         int SOFTWARE = 1; // 1 << 0
         int HARDWARE = 2; // 1 << 1
@@ -122,7 +127,7 @@
             AccessibilityServiceFragmentType.INTUITIVE,
             AccessibilityServiceFragmentType.BOUNCE,
     })
-    public @interface AccessibilityServiceFragmentType {
+    private @interface AccessibilityServiceFragmentType {
         int LEGACY = 0;
         int INVISIBLE = 1;
         int INTUITIVE = 2;
@@ -140,11 +145,61 @@
             ShortcutMenuMode.LAUNCH,
             ShortcutMenuMode.EDIT,
     })
-    public @interface ShortcutMenuMode {
+    private @interface ShortcutMenuMode {
         int LAUNCH = 0;
         int EDIT = 1;
     }
 
+    /**
+     * Annotation for align the element index of white listing feature
+     * {@code WHITE_LISTING_FEATURES}.
+     *
+     * {@code COMPONENT_ID} is to get the service component name.
+     * {@code LABEL_ID} is to get the service label text.
+     * {@code ICON_ID} is to get the service icon.
+     * {@code FRAGMENT_TYPE} is to get the service fragment type.
+     * {@code SETTINGS_KEY} is to get the service settings key.
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            WhiteListingFeatureElementIndex.COMPONENT_ID,
+            WhiteListingFeatureElementIndex.LABEL_ID,
+            WhiteListingFeatureElementIndex.ICON_ID,
+            WhiteListingFeatureElementIndex.FRAGMENT_TYPE,
+            WhiteListingFeatureElementIndex.SETTINGS_KEY,
+    })
+    @interface WhiteListingFeatureElementIndex {
+        int COMPONENT_ID = 0;
+        int LABEL_ID = 1;
+        int ICON_ID = 2;
+        int FRAGMENT_TYPE = 3;
+        int SETTINGS_KEY = 4;
+    }
+
+    private static final String[][] WHITE_LISTING_FEATURES = {
+            {
+                    COLOR_INVERSION_COMPONENT_NAME.flattenToString(),
+                    String.valueOf(R.string.color_inversion_feature_name),
+                    String.valueOf(R.drawable.ic_accessibility_color_inversion),
+                    String.valueOf(AccessibilityServiceFragmentType.INTUITIVE),
+                    Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED,
+            },
+            {
+                    DALTONIZER_COMPONENT_NAME.flattenToString(),
+                    String.valueOf(R.string.color_correction_feature_name),
+                    String.valueOf(R.drawable.ic_accessibility_color_correction),
+                    String.valueOf(AccessibilityServiceFragmentType.INTUITIVE),
+                    Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
+            },
+            {
+                    MAGNIFICATION_CONTROLLER_NAME,
+                    String.valueOf(R.string.accessibility_magnification_chooser_text),
+                    String.valueOf(R.drawable.ic_accessibility_magnification),
+                    String.valueOf(AccessibilityServiceFragmentType.INVISIBLE),
+                    Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
+            },
+    };
+
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -199,6 +254,20 @@
 
     private static List<AccessibilityButtonTarget> getServiceTargets(@NonNull Context context,
             @ShortcutType int shortcutType) {
+        final List<AccessibilityButtonTarget> targets = new ArrayList<>();
+        targets.addAll(getAccessibilityServiceTargets(context));
+        targets.addAll(getWhiteListingServiceTargets(context));
+
+        final AccessibilityManager ams = (AccessibilityManager) context.getSystemService(
+                Context.ACCESSIBILITY_SERVICE);
+        final List<String> requiredTargets = ams.getAccessibilityShortcutTargets(shortcutType);
+        targets.removeIf(target -> !requiredTargets.contains(target.getId()));
+
+        return targets;
+    }
+
+    private static List<AccessibilityButtonTarget> getAccessibilityServiceTargets(
+            @NonNull Context context) {
         final AccessibilityManager ams = (AccessibilityManager) context.getSystemService(
                 Context.ACCESSIBILITY_SERVICE);
         final List<AccessibilityServiceInfo> installedServices =
@@ -209,29 +278,74 @@
 
         final List<AccessibilityButtonTarget> targets = new ArrayList<>(installedServices.size());
         for (AccessibilityServiceInfo info : installedServices) {
-            if ((info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0) {
-                targets.add(new AccessibilityButtonTarget(context, info));
-            }
-        }
-
-        final List<String> requiredTargets = ams.getAccessibilityShortcutTargets(shortcutType);
-        targets.removeIf(target -> !requiredTargets.contains(target.getId()));
-
-        // TODO(b/146815874): Will replace it with white list services.
-        if (Settings.Secure.getInt(context.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, 0) == 1) {
-            final AccessibilityButtonTarget magnificationTarget = new AccessibilityButtonTarget(
-                    context,
-                    MAGNIFICATION_COMPONENT_ID,
-                    R.string.accessibility_magnification_chooser_text,
-                    R.drawable.ic_accessibility_magnification,
-                    AccessibilityServiceFragmentType.INTUITIVE);
-            targets.add(magnificationTarget);
+            targets.add(new AccessibilityButtonTarget(context, info));
         }
 
         return targets;
     }
 
+    private static List<AccessibilityButtonTarget> getWhiteListingServiceTargets(
+            @NonNull Context context) {
+        final List<AccessibilityButtonTarget> targets = new ArrayList<>();
+
+        for (int i = 0; i < WHITE_LISTING_FEATURES.length; i++) {
+            final AccessibilityButtonTarget target = new AccessibilityButtonTarget(
+                    context,
+                    WHITE_LISTING_FEATURES[i][COMPONENT_ID],
+                    Integer.parseInt(WHITE_LISTING_FEATURES[i][LABEL_ID]),
+                    Integer.parseInt(WHITE_LISTING_FEATURES[i][ICON_ID]),
+                    Integer.parseInt(WHITE_LISTING_FEATURES[i][FRAGMENT_TYPE]));
+            targets.add(target);
+        }
+
+        return targets;
+    }
+
+    private static boolean isWhiteListingServiceEnabled(@NonNull Context context,
+            AccessibilityButtonTarget target) {
+
+        for (int i = 0; i < WHITE_LISTING_FEATURES.length; i++) {
+            if (WHITE_LISTING_FEATURES[i][COMPONENT_ID].equals(target.getId())) {
+                return Settings.Secure.getInt(context.getContentResolver(),
+                        WHITE_LISTING_FEATURES[i][SETTINGS_KEY],
+                        /* settingsValueOff */ 0) == /* settingsValueOn */ 1;
+            }
+        }
+
+        return false;
+    }
+
+    private static boolean isWhiteListingService(String componentId) {
+        for (int i = 0; i < WHITE_LISTING_FEATURES.length; i++) {
+            if (WHITE_LISTING_FEATURES[i][COMPONENT_ID].equals(componentId)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private void setWhiteListingServiceEnabled(String componentId, int settingsValue) {
+        for (int i = 0; i < WHITE_LISTING_FEATURES.length; i++) {
+            if (WHITE_LISTING_FEATURES[i][COMPONENT_ID].equals(componentId)) {
+                Settings.Secure.putInt(getContentResolver(),
+                        WHITE_LISTING_FEATURES[i][SETTINGS_KEY], settingsValue);
+                return;
+            }
+        }
+    }
+
+    private void disableService(ComponentName componentName) {
+        final String componentId = componentName.flattenToString();
+
+        if (isWhiteListingService(componentId)) {
+            setWhiteListingServiceEnabled(componentName.flattenToString(),
+                    /* settingsValueOff */ 0);
+        } else {
+            setAccessibilityServiceState(this, componentName, /* enabled= */ false);
+        }
+    }
+
     private static class ViewHolder {
         ImageView mIconView;
         TextView mLabelView;
@@ -350,11 +464,14 @@
         private void updateIntuitiveActionItemVisibility(@NonNull Context context,
                 @NonNull ViewHolder holder, AccessibilityButtonTarget target) {
             final boolean isEditMenuMode = (mShortcutMenuMode == ShortcutMenuMode.EDIT);
+            final boolean isServiceEnabled = isWhiteListingService(target.getId())
+                    ? isWhiteListingServiceEnabled(context, target)
+                    : isAccessibilityServiceEnabled(context, target);
 
             holder.mViewItem.setImageDrawable(context.getDrawable(R.drawable.ic_delete_item));
             holder.mViewItem.setVisibility(isEditMenuMode ? View.VISIBLE : View.GONE);
             holder.mSwitchItem.setVisibility(isEditMenuMode ? View.GONE : View.VISIBLE);
-            holder.mSwitchItem.setChecked(!isEditMenuMode && isServiceEnabled(context, target));
+            holder.mSwitchItem.setChecked(!isEditMenuMode && isServiceEnabled);
             holder.mItemContainer.setVisibility(View.VISIBLE);
         }
 
@@ -411,7 +528,7 @@
         }
     }
 
-    private static boolean isServiceEnabled(@NonNull Context context,
+    private static boolean isAccessibilityServiceEnabled(@NonNull Context context,
             AccessibilityButtonTarget target) {
         final AccessibilityManager ams = (AccessibilityManager) context.getSystemService(
                 Context.ACCESSIBILITY_SERVICE);
@@ -429,13 +546,65 @@
     }
 
     private void onTargetSelected(AdapterView<?> parent, View view, int position, long id) {
-        Settings.Secure.putString(getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
-                mTargets.get(position).getId());
-        // TODO(b/146969684): notify accessibility button clicked.
+        final AccessibilityButtonTarget target = mTargets.get(position);
+        switch (target.getFragmentType()) {
+            case AccessibilityServiceFragmentType.LEGACY:
+                onLegacyTargetSelected(target);
+                break;
+            case AccessibilityServiceFragmentType.INVISIBLE:
+                onInvisibleTargetSelected(target);
+                break;
+            case AccessibilityServiceFragmentType.INTUITIVE:
+                onIntuitiveTargetSelected(target);
+                break;
+            case AccessibilityServiceFragmentType.BOUNCE:
+                // Do nothing
+                break;
+            default:
+                throw new IllegalStateException("Unexpected fragment type");
+        }
+
         mAlertDialog.dismiss();
     }
 
+    private void onLegacyTargetSelected(AccessibilityButtonTarget target) {
+        if (mShortcutType == ACCESSIBILITY_BUTTON) {
+            final AccessibilityManager ams = (AccessibilityManager) getSystemService(
+                    Context.ACCESSIBILITY_SERVICE);
+            ams.notifyAccessibilityButtonClicked(getDisplayId(), target.getId());
+        } else if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
+            switchServiceState(target);
+        } else {
+            throw new IllegalArgumentException("Unsupported shortcut type:" + mShortcutType);
+        }
+    }
+
+    private void onInvisibleTargetSelected(AccessibilityButtonTarget target) {
+        final AccessibilityManager ams = (AccessibilityManager) getSystemService(
+                Context.ACCESSIBILITY_SERVICE);
+        ams.notifyAccessibilityButtonClicked(getDisplayId(), target.getId());
+    }
+
+    private void onIntuitiveTargetSelected(AccessibilityButtonTarget target) {
+        switchServiceState(target);
+    }
+
+    private void switchServiceState(AccessibilityButtonTarget target) {
+        final ComponentName componentName =
+                ComponentName.unflattenFromString(target.getId());
+        final String componentId = componentName.flattenToString();
+
+        if (isWhiteListingService(componentId)) {
+            setWhiteListingServiceEnabled(componentId,
+                    isWhiteListingServiceEnabled(this, target)
+                            ? /* settingsValueOff */ 0
+                            : /* settingsValueOn */ 1);
+        } else {
+            setAccessibilityServiceState(this, componentName,
+                    /* enabled= */!isAccessibilityServiceEnabled(this, target));
+        }
+    }
+
     private void onTargetDeleted(AdapterView<?> parent, View view, int position, long id) {
         final AccessibilityButtonTarget target = mTargets.get(position);
         final ComponentName targetComponentName =
@@ -470,14 +639,14 @@
 
             if (!hasValueInSettings(this,
                     ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE, componentName)) {
-                setAccessibilityServiceState(this, componentName, /* enabled= */ false);
+                disableService(componentName);
             }
         } else if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
             optOutValueFromSettings(this, ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE, componentName);
 
             if (!hasValueInSettings(this,
                     ACCESSIBILITY_BUTTON_USER_TYPE, componentName)) {
-                setAccessibilityServiceState(this, componentName, /* enabled= */ false);
+                disableService(componentName);
             }
         } else {
             throw new IllegalArgumentException("Unsupported shortcut type:" + mShortcutType);
diff --git a/core/java/com/android/internal/app/BlockedAppActivity.java b/core/java/com/android/internal/app/BlockedAppActivity.java
new file mode 100644
index 0000000..fbdbbfb
--- /dev/null
+++ b/core/java/com/android/internal/app/BlockedAppActivity.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.R;
+
+/**
+ * A dialog shown to the user when they try to launch an app that is not allowed in lock task
+ * mode. The intent to start this activity must be created with the static factory method provided
+ * below.
+ */
+public class BlockedAppActivity extends AlertActivity {
+
+    private static final String TAG = "BlockedAppActivity";
+    private static final String PACKAGE_NAME = "com.android.internal.app";
+    private static final String EXTRA_BLOCKED_PACKAGE = PACKAGE_NAME + ".extra.BLOCKED_PACKAGE";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Intent intent = getIntent();
+        int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, /* defaultValue= */ -1);
+        if (userId < 0) {
+            Slog.wtf(TAG, "Invalid user: " + userId);
+            finish();
+            return;
+        }
+
+        String packageName = intent.getStringExtra(EXTRA_BLOCKED_PACKAGE);
+        if (TextUtils.isEmpty(packageName)) {
+            Slog.wtf(TAG, "Invalid package: " + packageName);
+            finish();
+            return;
+        }
+
+        CharSequence appLabel = getAppLabel(userId, packageName);
+
+        mAlertParams.mTitle = getString(R.string.app_blocked_title);
+        mAlertParams.mMessage = getString(R.string.app_blocked_message, appLabel);
+        mAlertParams.mPositiveButtonText = getString(android.R.string.ok);
+        setupAlert();
+    }
+
+    private CharSequence getAppLabel(int userId, String packageName) {
+        PackageManager pm = getPackageManager();
+        try {
+            ApplicationInfo aInfo =
+                    pm.getApplicationInfoAsUser(packageName, /* flags= */ 0, userId);
+            return aInfo.loadLabel(pm);
+        } catch (PackageManager.NameNotFoundException ne) {
+            Slog.e(TAG, "Package " + packageName + " not found", ne);
+        }
+        return packageName;
+    }
+
+
+    /** Creates an intent that launches {@link BlockedAppActivity}. */
+    public static Intent createIntent(int userId, String packageName) {
+        return new Intent()
+                .setClassName("android", BlockedAppActivity.class.getName())
+                .putExtra(Intent.EXTRA_USER_ID, userId)
+                .putExtra(EXTRA_BLOCKED_PACKAGE, packageName);
+    }
+}
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 53327bc..c4ee195 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -2627,7 +2627,7 @@
     gMidAudioRecordRoutingProxy_release =
             android::GetMethodIDOrDie(env, gClsAudioRecordRoutingProxy, "native_release", "()V");
 
-    AudioSystem::setErrorCallback(android_media_AudioSystem_error_callback);
+    AudioSystem::addErrorCallback(android_media_AudioSystem_error_callback);
 
     RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
     return RegisterMethodsOrDie(env, kEventHandlerClassPathName, gEventHandlerMethods,
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index 59dab0c8..649e5d2 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -40,10 +40,15 @@
 
 static const bool kDebugDispatchCycle = false;
 
+static const char* toString(bool value) {
+    return value ? "true" : "false";
+}
+
 static struct {
     jclass clazz;
 
     jmethodID dispatchInputEvent;
+    jmethodID onFocusEvent;
     jmethodID dispatchBatchedInputEventPending;
 } gInputEventReceiverClassInfo;
 
@@ -219,8 +224,7 @@
         bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {
     if (kDebugDispatchCycle) {
         ALOGD("channel '%s' ~ Consuming input events, consumeBatches=%s, frameTime=%" PRId64,
-                getInputChannelName().c_str(),
-                consumeBatches ? "true" : "false", frameTime);
+              getInputChannelName().c_str(), toString(consumeBatches), frameTime);
     }
 
     if (consumeBatches) {
@@ -235,6 +239,7 @@
     for (;;) {
         uint32_t seq;
         InputEvent* inputEvent;
+
         status_t status = mInputConsumer.consume(&mInputEventFactory,
                 consumeBatches, frameTime, &seq, &inputEvent);
         if (status) {
@@ -302,6 +307,19 @@
                 inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent);
                 break;
             }
+            case AINPUT_EVENT_TYPE_FOCUS: {
+                FocusEvent* focusEvent = static_cast<FocusEvent*>(inputEvent);
+                if (kDebugDispatchCycle) {
+                    ALOGD("channel '%s' ~ Received focus event: hasFocus=%s, inTouchMode=%s.",
+                          getInputChannelName().c_str(), toString(focusEvent->getHasFocus()),
+                          toString(focusEvent->getInTouchMode()));
+                }
+                env->CallVoidMethod(receiverObj.get(), gInputEventReceiverClassInfo.onFocusEvent,
+                                    jboolean(focusEvent->getHasFocus()),
+                                    jboolean(focusEvent->getInTouchMode()));
+                finishInputEvent(seq, true /* handled */);
+                return OK;
+            }
 
             default:
                 assert(false); // InputConsumer should prevent this from ever happening
@@ -421,6 +439,8 @@
     gInputEventReceiverClassInfo.dispatchInputEvent = GetMethodIDOrDie(env,
             gInputEventReceiverClassInfo.clazz,
             "dispatchInputEvent", "(ILandroid/view/InputEvent;)V");
+    gInputEventReceiverClassInfo.onFocusEvent =
+            GetMethodIDOrDie(env, gInputEventReceiverClassInfo.clazz, "onFocusEvent", "(ZZ)V");
     gInputEventReceiverClassInfo.dispatchBatchedInputEventPending = GetMethodIDOrDie(env,
             gInputEventReceiverClassInfo.clazz, "dispatchBatchedInputEventPending", "()V");
 
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 673772a..39ea45a 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -1431,7 +1431,7 @@
   // Isolated process / webview / app zygote should be gated by SELinux and file permission
   // so they can't even traverse CE / DE directories.
   if (pkg_data_info_list != nullptr
-      && GetBoolProperty(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, false)) {
+      && GetBoolProperty(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, true)) {
     isolateAppData(env, pkg_data_info_list, uid, process_name, managed_nice_name,
         fail_fn);
   }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 7ecd9b7..2665e8a 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4976,6 +4976,12 @@
                 android:process=":ui">
         </activity>
 
+        <activity android:name="com.android.internal.app.BlockedAppActivity"
+                android:theme="@style/Theme.Dialog.Confirmation"
+                android:excludeFromRecents="true"
+                android:process=":ui">
+        </activity>
+
         <activity android:name="com.android.settings.notification.NotificationAccessConfirmationActivity"
                   android:theme="@style/Theme.Dialog.Confirmation"
                   android:excludeFromRecents="true">
diff --git a/core/res/res/drawable/ic_accessibility_color_correction.xml b/core/res/res/drawable/ic_accessibility_color_correction.xml
new file mode 100644
index 0000000..02fa4b8
--- /dev/null
+++ b/core/res/res/drawable/ic_accessibility_color_correction.xml
@@ -0,0 +1,37 @@
+<vector android:height="24dp" android:viewportHeight="192"
+    android:viewportWidth="192" android:width="24dp"
+    xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#00BCD4" android:pathData="M37.14,173.74L8.61,83.77c-1.72,-5.75 0.26,-11.97 4.97,-15.63L87.15,11c5.2,-4.04 12.46,-3.99 17.61,0.12l73.81,58.94c4.63,3.7 6.53,9.88 4.8,15.57l-28.48,88.18c-1.85,6.06 -7.39,10.19 -13.67,10.19H50.84C44.53,184 38.97,179.83 37.14,173.74z"/>
+    <path android:fillAlpha="0.2" android:fillColor="#FFFFFF"
+        android:pathData="M13.58,69.14L87.15,12c5.2,-4.04 12.46,-3.99 17.61,0.12l73.81,58.94c3.36,2.68 5.27,6.67 5.41,10.82c0.15,-4.51 -1.79,-8.93 -5.41,-11.82l-73.81,-58.94C99.61,7.01 92.35,6.96 87.15,11L13.58,68.14c-3.71,2.88 -5.72,7.36 -5.56,11.94C8.17,75.85 10.14,71.81 13.58,69.14z" android:strokeAlpha="0.2"/>
+    <path android:fillAlpha="0.2" android:fillColor="#263238"
+        android:pathData="M183.35,84.63l-28.48,88.18c-1.85,6.06 -7.39,10.19 -13.67,10.19H50.84c-6.31,0 -11.87,-4.17 -13.7,-10.26L8.61,82.77c-0.36,-1.22 -0.55,-2.46 -0.59,-3.69c-0.06,1.56 0.13,3.14 0.59,4.69l28.53,89.97c1.83,6.09 7.39,10.26 13.7,10.26h90.37c6.28,0 11.82,-4.13 13.67,-10.19l28.48,-88.18c0.48,-1.57 0.67,-3.17 0.61,-4.75C183.92,82.13 183.73,83.39 183.35,84.63z" android:strokeAlpha="0.2"/>
+    <path android:pathData="M60.01,135L60.01,135H60l48.04,49h33.17c6.28,0 11.82,-4.13 13.67,-10.19l18.68,-57.84L129.51,72.2l-0.01,0c0.27,0.27 0.52,0.5 0.77,0.77l0.55,0.55c1.57,1.56 1.57,4.08 -0.04,5.68l-12.56,12.49l6.36,6.3l1.38,1.37l-5.67,5.64l-5.71,-5.68L79.15,135H60.42H60.01z">
+        <aapt:attr name="android:fillColor">
+            <gradient android:endX="155.9627" android:endY="165.1493"
+                android:startX="98.4649" android:startY="107.6516" android:type="linear">
+                <item android:color="#19263238" android:offset="0"/>
+                <item android:color="#00212121" android:offset="1"/>
+            </gradient>
+        </aapt:attr>
+    </path>
+    <path android:fillColor="#0097A7" android:pathData="M68.55,120.35l32.173,-32.173l7.658,7.658l-32.173,32.173z"/>
+    <path android:fillColor="#FFFFFF" android:pathData="M130.83,73.52l-9.42,-9.36c-1.57,-1.56 -4.1,-1.56 -5.67,0l-12.56,12.48L95.42,69l-5.67,5.64l5.71,5.68L60,116.97V135h19.15l35.42,-35.68l5.71,5.68l5.67,-5.64l-7.73,-7.68l12.56,-12.48C132.4,77.6 132.4,75.08 130.83,73.52zM74.98,126.77l-6.43,-6.43l32.17,-32.17l6.43,6.43L74.98,126.77z"/>
+    <path android:fillAlpha="0.1" android:fillColor="#263238"
+        android:pathData="M120.28,105l-5.71,-5.68l-35.42,35.68l-19.15,0l1,1l19.15,0l35.42,-35.68l5.71,5.68l5.68,-5.64l-1,-1z" android:strokeAlpha="0.1"/>
+    <path android:fillAlpha="0.1" android:fillColor="#263238"
+        android:pathData="M90.75,75.64l-0.01,0l4.71,4.68l0.01,0z" android:strokeAlpha="0.1"/>
+    <path android:fillAlpha="0.1" android:fillColor="#263238"
+        android:pathData="M131.83,74.52l-0.97,-0.97c1.54,1.56 1.53,4.06 -0.07,5.65l-12.56,12.48l1,1l12.55,-12.48C133.4,78.6 133.4,76.08 131.83,74.52z" android:strokeAlpha="0.1"/>
+    <path android:fillAlpha="0.1" android:fillColor="#263238"
+        android:pathData="M101.72,89.17l6.67,6.66l0,0l-7.67,-7.66l-32.17,32.17l1,1z" android:strokeAlpha="0.1"/>
+    <path android:pathData="M37.13,173.7L8.62,83.69c-1.7,-5.8 0.3,-12 5,-15.6l73.52,-57.1c5.2,-4 12.5,-4 17.6,0.1l73.82,58.9c4.6,3.7 6.5,9.9 4.8,15.6l-28.51,88.21c-1.8,6.1 -7.4,10.2 -13.7,10.2H50.83C44.53,184 38.93,179.8 37.13,173.7z">
+        <aapt:attr name="android:fillColor">
+            <gradient android:centerX="21.977" android:centerY="23.8809"
+                android:gradientRadius="158.0384" android:type="radial">
+                <item android:color="#19FFFFFF" android:offset="0"/>
+                <item android:color="#00FFFFFF" android:offset="1"/>
+            </gradient>
+        </aapt:attr>
+    </path>
+</vector>
diff --git a/core/res/res/drawable/ic_accessibility_color_inversion.xml b/core/res/res/drawable/ic_accessibility_color_inversion.xml
new file mode 100644
index 0000000..97b30b0
--- /dev/null
+++ b/core/res/res/drawable/ic_accessibility_color_inversion.xml
@@ -0,0 +1,47 @@
+<vector android:height="24dp" android:viewportHeight="192"
+    android:viewportWidth="192" android:width="24dp"
+    xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#546E7A" android:pathData="M37.14,173.74L8.61,83.77c-1.72,-5.75 0.26,-11.97 4.97,-15.63L87.15,11c5.2,-4.04 12.46,-3.99 17.61,0.12l73.81,58.94c4.63,3.7 6.53,9.88 4.8,15.57l-28.48,88.18c-1.85,6.06 -7.39,10.19 -13.67,10.19H50.84C44.53,184 38.97,179.83 37.14,173.74z"/>
+    <path android:fillAlpha="0.2" android:fillColor="#263238"
+        android:pathData="M183.37,84.63l-28.48,88.18c-1.85,6.06 -7.39,10.19 -13.67,10.19H50.84c-6.31,0 -11.87,-4.17 -13.7,-10.26L8.61,82.77c-0.36,-1.22 -0.55,-2.46 -0.59,-3.69c-0.06,1.56 0.13,3.14 0.59,4.69l28.53,89.97c1.83,6.09 7.39,10.26 13.7,10.26h90.38c6.28,0 11.82,-4.13 13.67,-10.19l28.48,-88.18c0.48,-1.57 0.67,-3.17 0.61,-4.75C183.94,82.13 183.74,83.39 183.37,84.63z" android:strokeAlpha="0.2"/>
+    <path android:fillAlpha="0.2" android:fillColor="#FFFFFF"
+        android:pathData="M13.58,69.14L87.15,12c5.2,-4.04 12.46,-3.99 17.61,0.12l73.81,58.94c3.36,2.68 5.27,6.67 5.41,10.82c0.15,-4.51 -1.79,-8.93 -5.41,-11.82l-73.81,-58.94C99.61,7.01 92.35,6.96 87.15,11L13.58,68.14c-3.71,2.88 -5.72,7.36 -5.56,11.94C8.17,75.85 10.14,71.81 13.58,69.14z" android:strokeAlpha="0.2"/>
+    <path android:fillAlpha="0.2" android:fillColor="#FFFFFF"
+        android:pathData="M53,130.05l5.03,-4.79L44.16,112c-0.05,0.78 -0.14,1.52 -0.15,2.34C43.62,136.61 61.27,154.56 84,156v-5.31C70.13,149.64 58.56,141.54 53,130.05z" android:strokeAlpha="0.2"/>
+    <path android:fillAlpha="0.2" android:fillColor="#FFFFFF"
+        android:pathData="M109,52v5.31c13.65,1.05 24.67,9.15 30.11,20.64l-4.92,4.79L147.81,96c0.09,-0.78 0.17,-1.53 0.19,-2.34C148.38,71.39 131.36,53.44 109,52z" android:strokeAlpha="0.2"/>
+    <path android:pathData="M154.89,173.81l13.57,-42.02l-46.53,-46.56C125.75,90.5 128,96.98 128,104c0,17.7 -14.3,32 -32,32c-8.64,0 -16.47,-3.42 -22.22,-8.97l0.9,0.91L130.73,184h10.49C147.5,184 153.04,179.87 154.89,173.81z">
+        <aapt:attr name="android:fillColor">
+            <gradient android:endX="153.3523" android:endY="161.6371"
+                android:startX="90.6075" android:startY="98.8923" android:type="linear">
+                <item android:color="#33263238" android:offset="0"/>
+                <item android:color="#05263238" android:offset="1"/>
+            </gradient>
+        </aapt:attr>
+    </path>
+    <path android:pathData="M96,129.6V78.4c-14.11,0 -25.6,11.49 -25.6,25.6S81.89,129.6 96,129.6z">
+        <aapt:attr name="android:fillColor">
+            <gradient android:endX="150.8492" android:endY="164.1401"
+                android:startX="88.1044" android:startY="101.3954" android:type="linear">
+                <item android:color="#33263238" android:offset="0"/>
+                <item android:color="#05263238" android:offset="1"/>
+            </gradient>
+        </aapt:attr>
+    </path>
+    <path android:fillAlpha="0.2" android:fillColor="#263238"
+        android:pathData="M96,136c-17.53,0 -31.72,-14.04 -31.99,-31.5c0,0.17 -0.01,0.33 -0.01,0.5c0,17.7 14.3,32 32,32s32,-14.3 32,-32c0,-0.17 -0.01,-0.33 -0.01,-0.5C127.72,121.96 113.53,136 96,136z" android:strokeAlpha="0.2"/>
+    <path android:fillAlpha="0.2" android:fillColor="#263238"
+        android:pathData="M70.4,104c0,0.17 0.01,0.33 0.01,0.5C70.68,90.62 82.06,79.4 96,79.4v-1C81.89,78.4 70.4,89.88 70.4,104z" android:strokeAlpha="0.2"/>
+    <path android:fillAlpha="0.2" android:fillColor="#FFFFFF"
+        android:pathData="M96,72c-17.7,0 -32,14.3 -32,32s14.3,32 32,32s32,-14.3 32,-32S113.7,72 96,72zM70.4,104c0,-14.11 11.49,-25.6 25.6,-25.6v51.2C81.89,129.6 70.4,118.11 70.4,104z" android:strokeAlpha="0.2"/>
+    <path android:fillColor="#FFFFFF" android:pathData="M96,72c-17.7,0 -32,14.3 -32,32s14.3,32 32,32s32,-14.3 32,-32S113.7,72 96,72zM70.4,104c0,-14.11 11.49,-25.6 25.6,-25.6v51.2C81.89,129.6 70.4,118.11 70.4,104z"/>
+    <path android:pathData="M37.14,173.74L8.61,83.77c-1.72,-5.75 0.26,-11.97 4.97,-15.63L87.15,11c5.2,-4.04 12.46,-3.99 17.61,0.12l73.81,58.94c4.63,3.7 6.53,9.88 4.8,15.57l-28.48,88.18c-1.85,6.06 -7.39,10.19 -13.67,10.19H50.84C44.53,184 38.97,179.83 37.14,173.74z">
+        <aapt:attr name="android:fillColor">
+            <gradient android:endX="156.2451" android:endY="171.4516"
+                android:startX="37.0633" android:startY="52.269802" android:type="linear">
+                <item android:color="#19FFFFFF" android:offset="0"/>
+                <item android:color="#00FFFFFF" android:offset="1"/>
+            </gradient>
+        </aapt:attr>
+    </path>
+</vector>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 9c08728..4475415 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3752,6 +3752,8 @@
              </p>
          -->
         <attr name="canRequestFingerprintGestures" format="boolean" />
+        <!-- Attribute whether the accessibility service wants to be able to take screenshot. -->
+        <attr name="canTakeScreenshot" format="boolean" />
 
         <!-- Animated image of the accessibility service purpose or behavior, to help users
              understand how the service can help them.-->
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index bf7558c..d437aa1 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -777,4 +777,9 @@
     <!-- Assistant handles -->
     <dimen name="assist_handle_shadow_radius">2dp</dimen>
 
+    <!-- For Waterfall Display -->
+    <dimen name="waterfall_display_left_edge_size">0px</dimen>
+    <dimen name="waterfall_display_top_edge_size">0px</dimen>
+    <dimen name="waterfall_display_right_edge_size">0px</dimen>
+    <dimen name="waterfall_display_bottom_edge_size">0px</dimen>
 </resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 6cf6a68..9860830 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3007,6 +3007,7 @@
       <public name="featureId" />
       <public name="supportsInlineSuggestions" />
       <public name="crossProfile" />
+      <public name="canTakeScreenshot"/>
     </public-group>
 
     <public-group type="drawable" first-id="0x010800b5">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 63509ac..4354c0d 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -794,6 +794,11 @@
     <string name="capability_desc_canCaptureFingerprintGestures">Can capture gestures performed on
         the device\'s fingerprint sensor.</string>
 
+    <!-- Title for the capability of an accessibility service to take screenshot. [CHAR LIMIT=32] -->
+    <string name="capability_title_canTakeScreenshot">Take screenshot</string>
+    <!-- Description for the capability of an accessibility service to take screenshot. [CHAR LIMIT=NONE] -->
+    <string name="capability_desc_canTakeScreenshot">Can take a screenshot of the display.</string>
+
     <!--  Permissions -->
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
@@ -4922,6 +4927,13 @@
     <!-- Title for button to turn on work profile. [CHAR LIMIT=NONE] -->
     <string name="work_mode_turn_on">Turn on</string>
 
+    <!-- Title of the dialog that is shown when the user tries to launch a suspended application [CHAR LIMIT=50] -->
+    <string name="app_blocked_title">App is not available</string>
+    <!-- Default message shown in the dialog that is shown when the user tries to launch a suspended application [CHAR LIMIT=NONE] -->
+    <string name="app_blocked_message">
+        <xliff:g id="app_name" example="Gmail">%1$s</xliff:g> is not available right now.
+    </string>
+
     <!-- Message displayed in dialog when app is too old to run on this verison of android. [CHAR LIMIT=NONE] -->
     <string name="deprecated_target_sdk_message">This app was built for an older version of Android and may not work properly. Try checking for updates, or contact the developer.</string>
     <!-- Title for button to see application detail in app store which it came from - it may allow user to update to newer version. [CHAR LIMIT=50] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 9e11749..30dbfc7 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3036,6 +3036,9 @@
   <java-symbol type="string" name="app_suspended_more_details" />
   <java-symbol type="string" name="app_suspended_default_message" />
 
+  <java-symbol type="string" name="app_blocked_title" />
+  <java-symbol type="string" name="app_blocked_message" />
+
   <!-- Used internally for assistant to launch activity transitions -->
   <java-symbol type="id" name="cross_task_transition" />
 
@@ -3214,6 +3217,8 @@
   <java-symbol type="string" name="edit_accessibility_shortcut_menu_button" />
   <java-symbol type="string" name="cancel_accessibility_shortcut_menu_button" />
 
+  <java-symbol type="drawable" name="ic_accessibility_color_inversion" />
+  <java-symbol type="drawable" name="ic_accessibility_color_correction" />
   <java-symbol type="drawable" name="ic_accessibility_magnification" />
 
   <java-symbol type="drawable" name="ic_delete_item" />
@@ -3801,5 +3806,14 @@
   <!-- Assistant handles -->
   <java-symbol type="dimen" name="assist_handle_shadow_radius" />
 
+  <!-- For Waterfall Display -->
+  <java-symbol type="dimen" name="waterfall_display_left_edge_size" />
+  <java-symbol type="dimen" name="waterfall_display_top_edge_size" />
+  <java-symbol type="dimen" name="waterfall_display_right_edge_size" />
+  <java-symbol type="dimen" name="waterfall_display_bottom_edge_size" />
+
+  <!-- Accessibility take screenshot -->
+  <java-symbol type="string" name="capability_desc_canTakeScreenshot" />
+  <java-symbol type="string" name="capability_title_canTakeScreenshot" />
 
 </resources>
diff --git a/core/tests/coretests/src/android/app/appsearch/AppSearchSchemaTest.java b/core/tests/coretests/src/android/app/appsearch/AppSearchSchemaTest.java
new file mode 100644
index 0000000..0be52c1
--- /dev/null
+++ b/core/tests/coretests/src/android/app/appsearch/AppSearchSchemaTest.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.appsearch;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.expectThrows;
+
+import android.app.appsearch.AppSearchSchema.IndexingConfig;
+import android.app.appsearch.AppSearchSchema.PropertyConfig;
+
+import androidx.test.filters.SmallTest;
+
+import com.google.android.icing.proto.IndexingConfig.TokenizerType;
+import com.google.android.icing.proto.PropertyConfigProto;
+import com.google.android.icing.proto.SchemaProto;
+import com.google.android.icing.proto.SchemaTypeConfigProto;
+import com.google.android.icing.proto.TermMatchType;
+
+import org.junit.Test;
+
+@SmallTest
+public class AppSearchSchemaTest {
+    @Test
+    public void testSuccess() {
+        AppSearchSchema schema = AppSearchSchema.newBuilder()
+                .addType(AppSearchSchema.newSchemaTypeBuilder("Email")
+                        .addProperty(AppSearchSchema.newPropertyBuilder("subject")
+                                .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                                .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                .setIndexingConfig(AppSearchSchema.newIndexingConfigBuilder()
+                                        .setTokenizerType(IndexingConfig.TOKENIZER_TYPE_PLAIN)
+                                        .setTermMatchType(IndexingConfig.TERM_MATCH_TYPE_PREFIX)
+                                        .build()
+                                ).build()
+                        ).addProperty(AppSearchSchema.newPropertyBuilder("body")
+                                .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                                .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                .setIndexingConfig(AppSearchSchema.newIndexingConfigBuilder()
+                                        .setTokenizerType(IndexingConfig.TOKENIZER_TYPE_PLAIN)
+                                        .setTermMatchType(IndexingConfig.TERM_MATCH_TYPE_PREFIX)
+                                        .build()
+                                ).build()
+                        ).build()
+
+                ).addType(AppSearchSchema.newSchemaTypeBuilder("MusicRecording")
+                        .addProperty(AppSearchSchema.newPropertyBuilder("artist")
+                                .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                                .setCardinality(PropertyConfig.CARDINALITY_REPEATED)
+                                .setIndexingConfig(AppSearchSchema.newIndexingConfigBuilder()
+                                        .setTokenizerType(IndexingConfig.TOKENIZER_TYPE_PLAIN)
+                                        .setTermMatchType(IndexingConfig.TERM_MATCH_TYPE_PREFIX)
+                                        .build()
+                                ).build()
+                        ).addProperty(AppSearchSchema.newPropertyBuilder("pubDate")
+                                .setDataType(PropertyConfig.DATA_TYPE_INT64)
+                                .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                .setIndexingConfig(AppSearchSchema.newIndexingConfigBuilder()
+                                        .setTokenizerType(IndexingConfig.TOKENIZER_TYPE_NONE)
+                                        .setTermMatchType(IndexingConfig.TERM_MATCH_TYPE_UNKNOWN)
+                                        .build()
+                                ).build()
+                        ).build()
+                ).build();
+
+        SchemaProto expectedProto = SchemaProto.newBuilder()
+                .addTypes(SchemaTypeConfigProto.newBuilder()
+                        .setSchemaType("Email")
+                        .addProperties(PropertyConfigProto.newBuilder()
+                                .setPropertyName("subject")
+                                .setDataType(PropertyConfigProto.DataType.Code.STRING)
+                                .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
+                                .setIndexingConfig(
+                                        com.google.android.icing.proto.IndexingConfig.newBuilder()
+                                                .setTokenizerType(TokenizerType.Code.PLAIN)
+                                                .setTermMatchType(TermMatchType.Code.PREFIX)
+                                )
+                        ).addProperties(PropertyConfigProto.newBuilder()
+                                .setPropertyName("body")
+                                .setDataType(PropertyConfigProto.DataType.Code.STRING)
+                                .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
+                                .setIndexingConfig(
+                                        com.google.android.icing.proto.IndexingConfig.newBuilder()
+                                                .setTokenizerType(TokenizerType.Code.PLAIN)
+                                                .setTermMatchType(TermMatchType.Code.PREFIX)
+                                )
+                        )
+
+                ).addTypes(SchemaTypeConfigProto.newBuilder()
+                        .setSchemaType("MusicRecording")
+                        .addProperties(PropertyConfigProto.newBuilder()
+                                .setPropertyName("artist")
+                                .setDataType(PropertyConfigProto.DataType.Code.STRING)
+                                .setCardinality(PropertyConfigProto.Cardinality.Code.REPEATED)
+                                .setIndexingConfig(
+                                        com.google.android.icing.proto.IndexingConfig.newBuilder()
+                                                .setTokenizerType(TokenizerType.Code.PLAIN)
+                                                .setTermMatchType(TermMatchType.Code.PREFIX)
+                                )
+                        ).addProperties(PropertyConfigProto.newBuilder()
+                                .setPropertyName("pubDate")
+                                .setDataType(PropertyConfigProto.DataType.Code.INT64)
+                                .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
+                                .setIndexingConfig(
+                                        com.google.android.icing.proto.IndexingConfig.newBuilder()
+                                                .setTokenizerType(TokenizerType.Code.NONE)
+                                                .setTermMatchType(TermMatchType.Code.UNKNOWN)
+                                )
+                        )
+                ).build();
+
+        assertThat(schema.getProto()).isEqualTo(expectedProto);
+    }
+
+    @Test
+    public void testInvalidEnums() {
+        PropertyConfig.Builder builder = AppSearchSchema.newPropertyBuilder("test");
+        assertThrows(IllegalArgumentException.class, () -> builder.setDataType(99));
+        assertThrows(IllegalArgumentException.class, () -> builder.setCardinality(99));
+    }
+
+    @Test
+    public void testMissingFields() {
+        PropertyConfig.Builder builder = AppSearchSchema.newPropertyBuilder("test");
+        Exception e = expectThrows(IllegalSchemaException.class, builder::build);
+        assertThat(e).hasMessageThat().contains("Missing field: dataType");
+
+        builder.setDataType(PropertyConfig.DATA_TYPE_DOCUMENT);
+        e = expectThrows(IllegalSchemaException.class, builder::build);
+        assertThat(e).hasMessageThat().contains("Missing field: schemaType");
+
+        builder.setSchemaType("TestType");
+        e = expectThrows(IllegalSchemaException.class, builder::build);
+        assertThat(e).hasMessageThat().contains("Missing field: cardinality");
+
+        builder.setCardinality(PropertyConfig.CARDINALITY_REPEATED);
+        builder.build();
+    }
+}
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
index 1bd52af..ade1e0d 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
@@ -46,7 +46,7 @@
     // The number of fields tested in the corresponding CTS AccessibilityNodeInfoTest:
     // See fullyPopulateAccessibilityNodeInfo, assertEqualsAccessibilityNodeInfo,
     // and assertAccessibilityNodeInfoCleared in that class.
-    private static final int NUM_MARSHALLED_PROPERTIES = 35;
+    private static final int NUM_MARSHALLED_PROPERTIES = 38;
 
     /**
      * The number of properties that are purposely not marshalled
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
index 93f4b51..f151b81 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
@@ -19,9 +19,11 @@
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.IAccessibilityServiceConnection;
 import android.content.pm.ParceledListSlice;
+import android.graphics.Bitmap;
 import android.graphics.Region;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.RemoteCallback;
 
 /**
  * Stub implementation of IAccessibilityServiceConnection so each test doesn't need to implement
@@ -143,4 +145,14 @@
     public IBinder getOverlayWindowToken(int displayId) {
         return null;
     }
+
+    public int getWindowIdForLeashToken(IBinder token) {
+        return -1;
+    }
+
+    public Bitmap takeScreenshot(int displayId) {
+        return null;
+    }
+
+    public void takeScreenshotWithCallback(int displayId, RemoteCallback callback) {}
 }
diff --git a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
index 8c9b4d0..a602fa3 100644
--- a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
+++ b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
@@ -32,6 +32,7 @@
 
 import android.app.Activity;
 import android.app.Instrumentation;
+import android.view.InputDevice;
 import android.view.MotionEvent;
 
 import androidx.test.InstrumentationRegistry;
@@ -282,6 +283,35 @@
     }
 
     @Test
+    public void testEditor_onTouchEvent_mouseDrag() throws Throwable {
+        String text = "testEditor_onTouchEvent_mouseDrag";
+        onView(withId(R.id.textview)).perform(replaceText(text));
+        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
+
+        TextView tv = mActivity.findViewById(R.id.textview);
+        Editor editor = tv.getEditorForTesting();
+
+        // Simulate a mouse click and drag. This should NOT trigger a cursor drag.
+        long event1Time = 1001;
+        MotionEvent event1 = mouseDownEvent(event1Time, event1Time, 20f, 30f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1));
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+        long event2Time = 1002;
+        MotionEvent event2 = mouseMoveEvent(event1Time, event2Time, 120f, 30f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event2));
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertTrue(editor.getSelectionController().isCursorBeingModified());
+
+        long event3Time = 1003;
+        MotionEvent event3 = mouseUpEvent(event1Time, event3Time, 120f, 30f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event3));
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+    }
+
+    @Test
     public void testEditor_onTouchEvent_cursorDrag() throws Throwable {
         String text = "testEditor_onTouchEvent_cursorDrag";
         onView(withId(R.id.textview)).perform(replaceText(text));
@@ -385,4 +415,25 @@
     private static MotionEvent moveEvent(long downTime, long eventTime, float x, float y) {
         return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 0);
     }
+
+    private static MotionEvent mouseDownEvent(long downTime, long eventTime, float x, float y) {
+        MotionEvent event = downEvent(downTime, eventTime, x, y);
+        event.setSource(InputDevice.SOURCE_MOUSE);
+        event.setButtonState(MotionEvent.BUTTON_PRIMARY);
+        return event;
+    }
+
+    private static MotionEvent mouseUpEvent(long downTime, long eventTime, float x, float y) {
+        MotionEvent event = upEvent(downTime, eventTime, x, y);
+        event.setSource(InputDevice.SOURCE_MOUSE);
+        event.setButtonState(0);
+        return event;
+    }
+
+    private static MotionEvent mouseMoveEvent(long downTime, long eventTime, float x, float y) {
+        MotionEvent event = moveEvent(downTime, eventTime, x, y);
+        event.setSource(InputDevice.SOURCE_MOUSE);
+        event.setButtonState(MotionEvent.BUTTON_PRIMARY);
+        return event;
+    }
 }
diff --git a/core/tests/coretests/src/android/widget/EditorTouchStateTest.java b/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
index 215d0b8..3dc001d 100644
--- a/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
+++ b/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
@@ -48,24 +48,32 @@
     }
 
     @Test
+    public void testIsDistanceWithin() throws Exception {
+        assertTrue(EditorTouchState.isDistanceWithin(0, 0, 0, 0, 8));
+        assertTrue(EditorTouchState.isDistanceWithin(3, 9, 5, 11, 8));
+        assertTrue(EditorTouchState.isDistanceWithin(5, 11, 3, 9, 8));
+        assertFalse(EditorTouchState.isDistanceWithin(5, 10, 5, 20, 8));
+    }
+
+    @Test
     public void testUpdate_singleTap() throws Exception {
         // Simulate an ACTION_DOWN event.
         long event1Time = 1000;
         MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
         mTouchState.update(event1, mConfig);
-        assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+        assertSingleTap(mTouchState, 20f, 30f, 0, 0);
 
         // Simulate an ACTION_UP event.
         long event2Time = 1001;
         MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
         mTouchState.update(event2, mConfig);
-        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
+        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
 
         // Generate an ACTION_DOWN event whose time is after the double-tap timeout.
         long event3Time = event2Time + ViewConfiguration.getDoubleTapTimeout() + 1;
         MotionEvent event3 = downEvent(event3Time, event3Time, 22f, 33f);
         mTouchState.update(event3, mConfig);
-        assertSingleTap(mTouchState, 22f, 33f, 20f, 30f, false);
+        assertSingleTap(mTouchState, 22f, 33f, 20f, 30f);
     }
 
     @Test
@@ -74,13 +82,13 @@
         long event1Time = 1000;
         MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
         mTouchState.update(event1, mConfig);
-        assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+        assertSingleTap(mTouchState, 20f, 30f, 0, 0);
 
         // Simulate an ACTION_UP event.
         long event2Time = 1001;
         MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
         mTouchState.update(event2, mConfig);
-        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
+        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
 
         // Generate an ACTION_DOWN event whose time is within the double-tap timeout.
         long event3Time = 1002;
@@ -96,13 +104,13 @@
         long event1Time = 1000;
         MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
         mTouchState.update(event1, mConfig);
-        assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+        assertSingleTap(mTouchState, 20f, 30f, 0, 0);
 
         // Simulate an ACTION_UP event.
         long event2Time = 1001;
         MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
         mTouchState.update(event2, mConfig);
-        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
+        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
 
         // Generate an ACTION_DOWN event whose time is within the double-tap timeout.
         long event3Time = 1002;
@@ -125,13 +133,13 @@
         long event1Time = 1000;
         MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
         mTouchState.update(event1, mConfig);
-        assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+        assertSingleTap(mTouchState, 20f, 30f, 0, 0);
 
         // Simulate an ACTION_UP event with a delay that's longer than the double-tap timeout.
         long event2Time = 1000 + ViewConfiguration.getDoubleTapTimeout() + 1;
         MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
         mTouchState.update(event2, mConfig);
-        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
+        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
 
         // Generate an ACTION_DOWN event whose time is within the double-tap timeout when
         // calculated from the last ACTION_UP event time. Even though the time between the last up
@@ -140,7 +148,7 @@
         long event3Time = event2Time + 1;
         MotionEvent event3 = downEvent(event3Time, event3Time, 22f, 33f);
         mTouchState.update(event3, mConfig);
-        assertSingleTap(mTouchState, 22f, 33f, 20f, 30f, false);
+        assertSingleTap(mTouchState, 22f, 33f, 20f, 30f);
     }
 
     @Test
@@ -149,19 +157,19 @@
         long event1Time = 1000;
         MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
         mTouchState.update(event1, mConfig);
-        assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+        assertSingleTap(mTouchState, 20f, 30f, 0, 0);
 
         // Simulate an ACTION_MOVE event.
         long event2Time = 1001;
         MotionEvent event2 = moveEvent(event1Time, event2Time, 200f, 31f);
         mTouchState.update(event2, mConfig);
-        assertSingleTap(mTouchState, 20f, 30f, 0, 0, true);
+        assertDrag(mTouchState, 20f, 30f, 0, 0, false);
 
         // Simulate an ACTION_UP event with a delay that's longer than the double-tap timeout.
         long event3Time = 5000;
         MotionEvent event3 = upEvent(event1Time, event3Time, 200f, 31f);
         mTouchState.update(event3, mConfig);
-        assertSingleTap(mTouchState, 20f, 30f, 200f, 31f, false);
+        assertSingleTap(mTouchState, 20f, 30f, 200f, 31f);
 
         // Generate an ACTION_DOWN event whose time is within the double-tap timeout when
         // calculated from the last ACTION_UP event time. Even though the time between the last up
@@ -170,7 +178,7 @@
         long event4Time = event3Time + 1;
         MotionEvent event4 = downEvent(event4Time, event4Time, 200f, 31f);
         mTouchState.update(event4, mConfig);
-        assertSingleTap(mTouchState, 200f, 31f, 200f, 31f, false);
+        assertSingleTap(mTouchState, 200f, 31f, 200f, 31f);
     }
 
     @Test
@@ -180,14 +188,14 @@
         MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
         event1.setSource(InputDevice.SOURCE_MOUSE);
         mTouchState.update(event1, mConfig);
-        assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+        assertSingleTap(mTouchState, 20f, 30f, 0, 0);
 
         // Simulate an ACTION_UP event.
         long event2Time = 1001;
         MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
         event2.setSource(InputDevice.SOURCE_MOUSE);
         mTouchState.update(event2, mConfig);
-        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
+        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
 
         // Generate a second ACTION_DOWN event whose time is within the double-tap timeout.
         long event3Time = 1002;
@@ -220,13 +228,13 @@
         long event1Time = 1000;
         MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
         mTouchState.update(event1, mConfig);
-        assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+        assertSingleTap(mTouchState, 20f, 30f, 0, 0);
 
         // Simulate an ACTION_UP event.
         long event2Time = 1001;
         MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
         mTouchState.update(event2, mConfig);
-        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
+        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
 
         // Generate a second ACTION_DOWN event whose time is within the double-tap timeout.
         long event3Time = 1002;
@@ -246,7 +254,7 @@
         long event5Time = 1004;
         MotionEvent event5 = downEvent(event5Time, event5Time, 22f, 32f);
         mTouchState.update(event5, mConfig);
-        assertSingleTap(mTouchState, 22f, 32f, 21f, 31f, false);
+        assertSingleTap(mTouchState, 22f, 32f, 21f, 31f);
     }
 
     @Test
@@ -255,13 +263,13 @@
         long event1Time = 1000;
         MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
         mTouchState.update(event1, mConfig);
-        assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+        assertSingleTap(mTouchState, 20f, 30f, 0, 0);
 
         // Simulate an ACTION_MOVE event whose location is not far enough to start a drag.
         long event2Time = 1001;
         MotionEvent event2 = moveEvent(event1Time, event2Time, 21f, 30f);
         mTouchState.update(event2, mConfig);
-        assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+        assertSingleTap(mTouchState, 20f, 30f, 0, 0);
 
         // Simulate another ACTION_MOVE event whose location is far enough to start a drag.
         int touchSlop = mConfig.getScaledTouchSlop();
@@ -270,21 +278,135 @@
         long event3Time = 1002;
         MotionEvent event3 = moveEvent(event3Time, event3Time, newX, newY);
         mTouchState.update(event3, mConfig);
-        assertSingleTap(mTouchState, 20f, 30f, 0, 0, true);
+        assertDrag(mTouchState, 20f, 30f, 0, 0, false);
 
         // Simulate an ACTION_UP event.
         long event4Time = 1003;
         MotionEvent event4 = upEvent(event3Time, event4Time, 200f, 300f);
         mTouchState.update(event4, mConfig);
-        assertSingleTap(mTouchState, 20f, 30f, 200f, 300f, false);
+        assertSingleTap(mTouchState, 20f, 30f, 200f, 300f);
     }
 
     @Test
-    public void testIsDistanceWithin() throws Exception {
-        assertTrue(EditorTouchState.isDistanceWithin(0, 0, 0, 0, 8));
-        assertTrue(EditorTouchState.isDistanceWithin(3, 9, 5, 11, 8));
-        assertTrue(EditorTouchState.isDistanceWithin(5, 11, 3, 9, 8));
-        assertFalse(EditorTouchState.isDistanceWithin(5, 10, 5, 20, 8));
+    public void testUpdate_drag_startsCloseToVerticalThenHorizontal() throws Exception {
+        // Simulate an ACTION_DOWN event.
+        long event1Time = 1001;
+        MotionEvent event1 = downEvent(event1Time, event1Time, 0f, 0f);
+        mTouchState.update(event1, mConfig);
+        assertSingleTap(mTouchState, 0f, 0f, 0, 0);
+
+        // Simulate an ACTION_MOVE event that is < 30 deg from vertical.
+        long event2Time = 1002;
+        MotionEvent event2 = moveEvent(event1Time, event2Time, 100f, 174f);
+        mTouchState.update(event2, mConfig);
+        assertDrag(mTouchState, 0f, 0f, 0, 0, true);
+
+        // Simulate another ACTION_MOVE event that is horizontal from the original down event.
+        // The value of `isDragCloseToVertical` should NOT change since it should only reflect the
+        // initial direction of movement.
+        long event3Time = 1003;
+        MotionEvent event3 = moveEvent(event1Time, event3Time, 200f, 0f);
+        mTouchState.update(event3, mConfig);
+        assertDrag(mTouchState, 0f, 0f, 0, 0, true);
+
+        // Simulate an ACTION_UP event.
+        long event4Time = 1004;
+        MotionEvent event4 = upEvent(event1Time, event4Time, 200f, 0f);
+        mTouchState.update(event4, mConfig);
+        assertSingleTap(mTouchState, 0f, 0f, 200f, 0f);
+    }
+
+    @Test
+    public void testUpdate_drag_startsHorizontalThenVertical() throws Exception {
+        // Simulate an ACTION_DOWN event.
+        long event1Time = 1001;
+        MotionEvent event1 = downEvent(event1Time, event1Time, 0f, 0f);
+        mTouchState.update(event1, mConfig);
+        assertSingleTap(mTouchState, 0f, 0f, 0, 0);
+
+        // Simulate an ACTION_MOVE event that is > 30 deg from vertical.
+        long event2Time = 1002;
+        MotionEvent event2 = moveEvent(event1Time, event2Time, 100f, 173f);
+        mTouchState.update(event2, mConfig);
+        assertDrag(mTouchState, 0f, 0f, 0, 0, false);
+
+        // Simulate another ACTION_MOVE event that is vertical from the original down event.
+        // The value of `isDragCloseToVertical` should NOT change since it should only reflect the
+        // initial direction of movement.
+        long event3Time = 1003;
+        MotionEvent event3 = moveEvent(event1Time, event3Time, 0f, 200f);
+        mTouchState.update(event3, mConfig);
+        assertDrag(mTouchState, 0f, 0f, 0, 0, false);
+
+        // Simulate an ACTION_UP event.
+        long event4Time = 1004;
+        MotionEvent event4 = upEvent(event1Time, event4Time, 0f, 200f);
+        mTouchState.update(event4, mConfig);
+        assertSingleTap(mTouchState, 0f, 0f, 0f, 200f);
+    }
+
+    @Test
+    public void testUpdate_cancelAfterDown() throws Exception {
+        // Simulate an ACTION_DOWN event.
+        long event1Time = 1001;
+        MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
+        mTouchState.update(event1, mConfig);
+        assertSingleTap(mTouchState, 20f, 30f, 0, 0);
+
+        // Simulate an ACTION_CANCEL event.
+        long event2Time = 1002;
+        MotionEvent event2 = cancelEvent(event1Time, event2Time, 20f, 30f);
+        mTouchState.update(event2, mConfig);
+        assertSingleTap(mTouchState, 20f, 30f, 0, 0);
+    }
+
+    @Test
+    public void testUpdate_cancelAfterDrag() throws Exception {
+        // Simulate an ACTION_DOWN event.
+        long event1Time = 1001;
+        MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
+        mTouchState.update(event1, mConfig);
+        assertSingleTap(mTouchState, 20f, 30f, 0, 0);
+
+        // Simulate another ACTION_MOVE event whose location is far enough to start a drag.
+        long event2Time = 1002;
+        MotionEvent event2 = moveEvent(event2Time, event2Time, 200f, 30f);
+        mTouchState.update(event2, mConfig);
+        assertDrag(mTouchState, 20f, 30f, 0, 0, false);
+
+        // Simulate an ACTION_CANCEL event.
+        long event3Time = 1003;
+        MotionEvent event3 = cancelEvent(event1Time, event3Time, 200f, 30f);
+        mTouchState.update(event3, mConfig);
+        assertSingleTap(mTouchState, 20f, 30f, 0, 0);
+    }
+
+    @Test
+    public void testUpdate_cancelAfterMultitap() throws Exception {
+        // Simulate an ACTION_DOWN event.
+        long event1Time = 1001;
+        MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
+        mTouchState.update(event1, mConfig);
+        assertSingleTap(mTouchState, 20f, 30f, 0, 0);
+
+        // Simulate an ACTION_UP event.
+        long event2Time = 1002;
+        MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
+        mTouchState.update(event2, mConfig);
+        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
+
+        // Generate an ACTION_DOWN event whose time is within the double-tap timeout.
+        long event3Time = 1003;
+        MotionEvent event3 = downEvent(event3Time, event3Time, 22f, 33f);
+        mTouchState.update(event3, mConfig);
+        assertMultiTap(mTouchState, 22f, 33f, 20f, 30f,
+                MultiTapStatus.DOUBLE_TAP, true);
+
+        // Simulate an ACTION_CANCEL event.
+        long event4Time = 1004;
+        MotionEvent event4 = cancelEvent(event3Time, event4Time, 20f, 30f);
+        mTouchState.update(event4, mConfig);
+        assertSingleTap(mTouchState, 22f, 33f, 20f, 30f);
     }
 
     private static MotionEvent downEvent(long downTime, long eventTime, float x, float y) {
@@ -299,8 +421,12 @@
         return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 0);
     }
 
+    private static MotionEvent cancelEvent(long downTime, long eventTime, float x, float y) {
+        return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_CANCEL, x, y, 0);
+    }
+
     private static void assertSingleTap(EditorTouchState touchState, float lastDownX,
-            float lastDownY, float lastUpX, float lastUpY, boolean isMovedEnoughForDrag) {
+            float lastDownY, float lastUpX, float lastUpY) {
         assertThat(touchState.getLastDownX(), is(lastDownX));
         assertThat(touchState.getLastDownY(), is(lastDownY));
         assertThat(touchState.getLastUpX(), is(lastUpX));
@@ -309,7 +435,21 @@
         assertThat(touchState.isTripleClick(), is(false));
         assertThat(touchState.isMultiTap(), is(false));
         assertThat(touchState.isMultiTapInSameArea(), is(false));
-        assertThat(touchState.isMovedEnoughForDrag(), is(isMovedEnoughForDrag));
+        assertThat(touchState.isMovedEnoughForDrag(), is(false));
+    }
+
+    private static void assertDrag(EditorTouchState touchState, float lastDownX,
+            float lastDownY, float lastUpX, float lastUpY, boolean isDragCloseToVertical) {
+        assertThat(touchState.getLastDownX(), is(lastDownX));
+        assertThat(touchState.getLastDownY(), is(lastDownY));
+        assertThat(touchState.getLastUpX(), is(lastUpX));
+        assertThat(touchState.getLastUpY(), is(lastUpY));
+        assertThat(touchState.isDoubleTap(), is(false));
+        assertThat(touchState.isTripleClick(), is(false));
+        assertThat(touchState.isMultiTap(), is(false));
+        assertThat(touchState.isMultiTapInSameArea(), is(false));
+        assertThat(touchState.isMovedEnoughForDrag(), is(true));
+        assertThat(touchState.isDragCloseToVertical(), is(isDragCloseToVertical));
     }
 
     private static void assertMultiTap(EditorTouchState touchState,
@@ -325,5 +465,6 @@
                 || multiTapStatus == MultiTapStatus.TRIPLE_CLICK));
         assertThat(touchState.isMultiTapInSameArea(), is(isMultiTapInSameArea));
         assertThat(touchState.isMovedEnoughForDrag(), is(false));
+        assertThat(touchState.isDragCloseToVertical(), is(false));
     }
 }
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
index aa55e08..a0cfb31 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
@@ -19,6 +19,7 @@
 import static android.widget.espresso.ContextMenuUtils.assertContextMenuContainsItemDisabled;
 import static android.widget.espresso.ContextMenuUtils.assertContextMenuContainsItemEnabled;
 import static android.widget.espresso.ContextMenuUtils.assertContextMenuIsNotDisplayed;
+import static android.widget.espresso.DragHandleUtils.assertNoSelectionHandles;
 import static android.widget.espresso.DragHandleUtils.onHandleView;
 import static android.widget.espresso.TextViewActions.mouseClick;
 import static android.widget.espresso.TextViewActions.mouseClickOnTextAtIndex;
@@ -47,7 +48,6 @@
 import android.view.textclassifier.TextClassifier;
 
 import androidx.test.filters.MediumTest;
-import androidx.test.filters.Suppress;
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -88,11 +88,6 @@
                 mouseDragOnText(helloWorld.indexOf("llo"), helloWorld.indexOf("ld!")));
         onView(withId(R.id.textview)).check(hasSelection("llo wor"));
 
-        onHandleView(com.android.internal.R.id.selection_start_handle)
-                .check(matches(isDisplayed()));
-        onHandleView(com.android.internal.R.id.selection_end_handle)
-                .check(matches(isDisplayed()));
-
         onView(withId(R.id.textview)).perform(mouseClickOnTextAtIndex(helloWorld.indexOf("w")));
         onView(withId(R.id.textview)).check(hasSelection(""));
     }
@@ -193,7 +188,6 @@
     }
 
     @Test
-    @Suppress // Consistently failing. b/29591177
     public void testDragAndDrop_longClick() {
         final String text = "abc def ghi.";
         onView(withId(R.id.textview)).perform(mouseClick());
@@ -395,4 +389,29 @@
                 mouseTripleClickAndDragOnText(text.indexOf("ird"), text.indexOf("First")));
         onView(withId(R.id.textview)).check(hasSelection(text));
     }
+
+    @Test
+    public void testSelectionHandlesDisplay() {
+        final String helloWorld = "Hello world!";
+        onView(withId(R.id.textview)).perform(mouseClick());
+        onView(withId(R.id.textview)).perform(replaceText(helloWorld));
+
+        onView(withId(R.id.textview)).perform(
+                mouseDragOnText(helloWorld.indexOf("llo"), helloWorld.indexOf("ld!")));
+        onView(withId(R.id.textview)).check(hasSelection("llo wor"));
+
+        // Confirm that selection handles are shown when there is a selection.
+        onHandleView(com.android.internal.R.id.selection_start_handle)
+                .check(matches(isDisplayed()));
+        onHandleView(com.android.internal.R.id.selection_end_handle)
+                .check(matches(isDisplayed()));
+
+        onView(withId(R.id.textview)).perform(mouseClickOnTextAtIndex(helloWorld.indexOf("w")));
+        onView(withId(R.id.textview)).check(hasSelection(""));
+
+        // Confirm that the selection handles are not shown when there is no selection. This
+        // assertion is slow so we only do it in this one test case. The rest of the tests just
+        // assert via `hasSelection("")`.
+        assertNoSelectionHandles();
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
index 82854e5..6784ede 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
@@ -348,7 +348,7 @@
         verify(mAlertDialog).show();
         verify(mAccessibilityManagerService, atLeastOnce()).getInstalledAccessibilityServiceList(
                 anyInt());
-        verify(mAccessibilityManagerService, times(0)).performAccessibilityShortcut();
+        verify(mAccessibilityManagerService, times(0)).performAccessibilityShortcut(null);
         verify(mFrameworkObjectProvider, times(0)).getTextToSpeech(any(), any());
     }
 
@@ -365,7 +365,7 @@
         assertEquals(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS,
                 mLayoutParams.privateFlags
                         & WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS);
-        verify(mAccessibilityManagerService, times(1)).performAccessibilityShortcut();
+        verify(mAccessibilityManagerService, times(1)).performAccessibilityShortcut(null);
     }
 
     @Test
@@ -433,7 +433,7 @@
 
         verifyZeroInteractions(mAlertDialogBuilder, mAlertDialog);
         verify(mToast).show();
-        verify(mAccessibilityManagerService).performAccessibilityShortcut();
+        verify(mAccessibilityManagerService).performAccessibilityShortcut(null);
     }
 
     @Test
@@ -459,7 +459,7 @@
         when(mServiceInfo.loadSummary(any())).thenReturn(null);
         Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1);
         getController().performAccessibilityShortcut();
-        verify(mAccessibilityManagerService).performAccessibilityShortcut();
+        verify(mAccessibilityManagerService).performAccessibilityShortcut(null);
     }
 
     @Test
@@ -471,7 +471,7 @@
         getController().performAccessibilityShortcut();
 
         verifyZeroInteractions(mToast);
-        verify(mAccessibilityManagerService).performAccessibilityShortcut();
+        verify(mAccessibilityManagerService).performAccessibilityShortcut(null);
     }
 
     @Test
@@ -485,7 +485,7 @@
         getController().performAccessibilityShortcut();
 
         verifyZeroInteractions(mToast);
-        verify(mAccessibilityManagerService).performAccessibilityShortcut();
+        verify(mAccessibilityManagerService).performAccessibilityShortcut(null);
     }
 
     @Test
diff --git a/data/etc/car/Android.bp b/data/etc/car/Android.bp
index 26a40d3..dfb7a16 100644
--- a/data/etc/car/Android.bp
+++ b/data/etc/car/Android.bp
@@ -135,3 +135,10 @@
     src: "com.android.car.floatingcardslauncher.xml",
     filename_from_src: true,
 }
+
+prebuilt_etc {
+    name: "privapp_whitelist_com.android.car.ui.paintbooth",
+    sub_dir: "permissions",
+    src: "com.android.car.ui.paintbooth.xml",
+    filename_from_src: true,
+}
diff --git a/data/etc/car/com.android.car.ui.paintbooth.xml b/data/etc/car/com.android.car.ui.paintbooth.xml
new file mode 100644
index 0000000..11bf304
--- /dev/null
+++ b/data/etc/car/com.android.car.ui.paintbooth.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<permissions>
+
+    <privapp-permissions package="com.android.car.ui.paintbooth">
+        <!-- For enabling/disabling, and getting list of RROs -->
+        <permission name="android.permission.CHANGE_OVERLAY_PACKAGES"/>
+        <!-- For showing the current active activity -->
+        <permission name="android.permission.REAL_GET_TASKS"/>
+        <!-- For getting list of RROs for current user -->
+        <permission name="android.permission.MANAGE_USERS"/>
+        <!-- For getting list of RROs for current user-->
+        <permission name="android.permission.INTERACT_ACROSS_USERS"/>
+    </privapp-permissions>
+</permissions>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 9930ea2..ad99ab3 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -245,6 +245,7 @@
         <permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/>
         <permission name="android.permission.TETHER_PRIVILEGED"/>
         <permission name="android.permission.UPDATE_APP_OPS_STATS"/>
+        <permission name="android.permission.UPDATE_DEVICE_STATS"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.server.telecom">
diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java
index ae90995..447f043 100644
--- a/graphics/java/android/graphics/FontFamily.java
+++ b/graphics/java/android/graphics/FontFamily.java
@@ -20,6 +20,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.res.AssetManager;
 import android.graphics.fonts.FontVariationAxis;
+import android.os.Build;
 import android.text.TextUtils;
 
 import dalvik.annotation.optimization.CriticalNative;
@@ -58,7 +59,8 @@
      *
      * This cannot be deleted because it's in use by AndroidX.
      */
-    @UnsupportedAppUsage(trackingBug = 123768928)
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+            publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
     public long mNativePtr;
 
     // Points native font family builder. Must be zero after freezing this family.
@@ -67,7 +69,8 @@
     /**
      * This cannot be deleted because it's in use by AndroidX.
      */
-    @UnsupportedAppUsage(trackingBug = 123768928)
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+            publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
     public FontFamily() {
         mBuilderPtr = nInitBuilder(null, 0);
         mNativeBuilderCleaner = sBuilderRegistry.registerNativeAllocation(this, mBuilderPtr);
@@ -76,7 +79,8 @@
     /**
      * This cannot be deleted because it's in use by AndroidX.
      */
-    @UnsupportedAppUsage(trackingBug = 123768928)
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+            publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
     public FontFamily(@Nullable String[] langs, int variant) {
         final String langsString;
         if (langs == null || langs.length == 0) {
@@ -98,7 +102,8 @@
      *
      * This cannot be deleted because it's in use by AndroidX.
      */
-    @UnsupportedAppUsage(trackingBug = 123768928)
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+            publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
     public boolean freeze() {
         if (mBuilderPtr == 0) {
             throw new IllegalStateException("This FontFamily is already frozen");
@@ -115,7 +120,8 @@
     /**
      * This cannot be deleted because it's in use by AndroidX.
      */
-    @UnsupportedAppUsage(trackingBug = 123768928)
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+            publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
     public void abortCreation() {
         if (mBuilderPtr == 0) {
             throw new IllegalStateException("This FontFamily is already frozen or abandoned");
@@ -127,7 +133,8 @@
     /**
      * This cannot be deleted because it's in use by AndroidX.
      */
-    @UnsupportedAppUsage(trackingBug = 123768928)
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+            publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
     public boolean addFont(String path, int ttcIndex, FontVariationAxis[] axes, int weight,
             int italic) {
         if (mBuilderPtr == 0) {
@@ -151,7 +158,8 @@
     /**
      * This cannot be deleted because it's in use by AndroidX.
      */
-    @UnsupportedAppUsage(trackingBug = 123768928)
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+            publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
     public boolean addFontFromBuffer(ByteBuffer font, int ttcIndex, FontVariationAxis[] axes,
             int weight, int italic) {
         if (mBuilderPtr == 0) {
@@ -179,7 +187,8 @@
      *
      * This cannot be deleted because it's in use by AndroidX.
      */
-    @UnsupportedAppUsage(trackingBug = 123768928)
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+            publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
     public boolean addFontFromAssetManager(AssetManager mgr, String path, int cookie,
             boolean isAsset, int ttcIndex, int weight, int isItalic,
             FontVariationAxis[] axes) {
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
index 17aacb9..fedde42 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
@@ -308,6 +308,9 @@
         if (spec.isStrongBoxBacked()) {
             flags |= KeyStore.FLAG_STRONGBOX;
         }
+        if (spec.isCriticalToDeviceEncryption()) {
+            flags |= KeyStore.FLAG_CRITICAL_TO_DEVICE_ENCRYPTION;
+        }
         String keyAliasInKeystore = Credentials.USER_PRIVATE_KEY + spec.getKeystoreAlias();
         KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics();
         boolean success = false;
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
index 91aac83..c52fd48 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -18,10 +18,8 @@
 
 import android.annotation.Nullable;
 import android.security.Credentials;
-import android.security.GateKeeper;
 import android.security.KeyPairGeneratorSpec;
 import android.security.KeyStore;
-import android.security.KeyStoreException;
 import android.security.keymaster.KeyCharacteristics;
 import android.security.keymaster.KeymasterArguments;
 import android.security.keymaster.KeymasterCertificateChain;
@@ -458,6 +456,9 @@
         if (mSpec.isStrongBoxBacked()) {
             flags |= KeyStore.FLAG_STRONGBOX;
         }
+        if (mSpec.isCriticalToDeviceEncryption()) {
+            flags |= KeyStore.FLAG_CRITICAL_TO_DEVICE_ENCRYPTION;
+        }
 
         byte[] additionalEntropy =
                 KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index 52ff9e0..450dd33 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -271,6 +271,7 @@
     private final boolean mIsStrongBoxBacked;
     private final boolean mUserConfirmationRequired;
     private final boolean mUnlockedDeviceRequired;
+    private final boolean mCriticalToDeviceEncryption;
     /*
      * ***NOTE***: All new fields MUST also be added to the following:
      * ParcelableKeyGenParameterSpec class.
@@ -307,7 +308,8 @@
             boolean invalidatedByBiometricEnrollment,
             boolean isStrongBoxBacked,
             boolean userConfirmationRequired,
-            boolean unlockedDeviceRequired) {
+            boolean unlockedDeviceRequired,
+            boolean criticalToDeviceEncryption) {
         if (TextUtils.isEmpty(keyStoreAlias)) {
             throw new IllegalArgumentException("keyStoreAlias must not be empty");
         }
@@ -357,6 +359,7 @@
         mIsStrongBoxBacked = isStrongBoxBacked;
         mUserConfirmationRequired = userConfirmationRequired;
         mUnlockedDeviceRequired = unlockedDeviceRequired;
+        mCriticalToDeviceEncryption = criticalToDeviceEncryption;
     }
 
     /**
@@ -710,6 +713,16 @@
     }
 
     /**
+     * Return whether this key is critical to the device encryption flow.
+     *
+     * @see android.security.KeyStore#FLAG_CRITICAL_TO_DEVICE_ENCRYPTION
+     * @hide
+     */
+    public boolean isCriticalToDeviceEncryption() {
+        return mCriticalToDeviceEncryption;
+    }
+
+    /**
      * Builder of {@link KeyGenParameterSpec} instances.
      */
     public final static class Builder {
@@ -741,6 +754,7 @@
         private boolean mIsStrongBoxBacked = false;
         private boolean mUserConfirmationRequired;
         private boolean mUnlockedDeviceRequired = false;
+        private boolean mCriticalToDeviceEncryption = false;
 
         /**
          * Creates a new instance of the {@code Builder}.
@@ -804,6 +818,7 @@
             mIsStrongBoxBacked = sourceSpec.isStrongBoxBacked();
             mUserConfirmationRequired = sourceSpec.isUserConfirmationRequired();
             mUnlockedDeviceRequired = sourceSpec.isUnlockedDeviceRequired();
+            mCriticalToDeviceEncryption = sourceSpec.isCriticalToDeviceEncryption();
         }
 
         /**
@@ -1339,6 +1354,20 @@
         }
 
         /**
+         * Set whether this key is critical to the device encryption flow
+         *
+         * This is a special flag only available to system servers to indicate the current key
+         * is part of the device encryption flow.
+         *
+         * @see android.security.KeyStore#FLAG_CRITICAL_TO_DEVICE_ENCRYPTION
+         * @hide
+         */
+        public Builder setCriticalToDeviceEncryption(boolean critical) {
+            mCriticalToDeviceEncryption = critical;
+            return this;
+        }
+
+        /**
          * Builds an instance of {@code KeyGenParameterSpec}.
          */
         @NonNull
@@ -1370,7 +1399,8 @@
                     mInvalidatedByBiometricEnrollment,
                     mIsStrongBoxBacked,
                     mUserConfirmationRequired,
-                    mUnlockedDeviceRequired);
+                    mUnlockedDeviceRequired,
+                    mCriticalToDeviceEncryption);
         }
     }
 }
diff --git a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
index d8030fb..98e4589 100644
--- a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
@@ -16,8 +16,8 @@
 
 package android.security.keystore;
 
-import android.os.Parcelable;
 import android.os.Parcel;
+import android.os.Parcelable;
 
 import java.math.BigInteger;
 import java.security.spec.AlgorithmParameterSpec;
@@ -105,6 +105,7 @@
         out.writeBoolean(mSpec.isStrongBoxBacked());
         out.writeBoolean(mSpec.isUserConfirmationRequired());
         out.writeBoolean(mSpec.isUnlockedDeviceRequired());
+        out.writeBoolean(mSpec.isCriticalToDeviceEncryption());
     }
 
     private static Date readDateOrNull(Parcel in) {
@@ -160,6 +161,7 @@
         final boolean isStrongBoxBacked = in.readBoolean();
         final boolean userConfirmationRequired = in.readBoolean();
         final boolean unlockedDeviceRequired = in.readBoolean();
+        final boolean criticalToDeviceEncryption = in.readBoolean();
         // The KeyGenParameterSpec is intentionally not constructed using a Builder here:
         // The intention is for this class to break if new parameters are added to the
         // KeyGenParameterSpec constructor (whereas using a builder would silently drop them).
@@ -190,7 +192,8 @@
                 invalidatedByBiometricEnrollment,
                 isStrongBoxBacked,
                 userConfirmationRequired,
-                unlockedDeviceRequired);
+                unlockedDeviceRequired,
+                criticalToDeviceEncryption);
     }
 
     public static final @android.annotation.NonNull Creator<ParcelableKeyGenParameterSpec> CREATOR = new Creator<ParcelableKeyGenParameterSpec>() {
diff --git a/keystore/tests/src/android/security/ParcelableKeyGenParameterSpecTest.java b/keystore/tests/src/android/security/ParcelableKeyGenParameterSpecTest.java
index fca2775..b7d72fc 100644
--- a/keystore/tests/src/android/security/ParcelableKeyGenParameterSpecTest.java
+++ b/keystore/tests/src/android/security/ParcelableKeyGenParameterSpecTest.java
@@ -84,6 +84,7 @@
                 .setIsStrongBoxBacked(true)
                 .setUserConfirmationRequired(true)
                 .setUnlockedDeviceRequired(true)
+                .setCriticalToDeviceEncryption(true)
                 .build();
     }
 
@@ -115,6 +116,7 @@
         assertThat(spec.isStrongBoxBacked(), is(true));
         assertThat(spec.isUserConfirmationRequired(), is(true));
         assertThat(spec.isUnlockedDeviceRequired(), is(true));
+        assertThat(spec.isCriticalToDeviceEncryption(), is(true));
     }
 
     private Parcel parcelForReading(ParcelableKeyGenParameterSpec spec) {
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 12681ae..5790150 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -217,13 +217,16 @@
         canvas->setMatrix(mMatrix);
         switch (mType) {
             case Type::Rect:
-                canvas->clipRect(mRRect.rect(), mOp);
+                // Don't anti-alias rectangular clips
+                canvas->clipRect(mRRect.rect(), mOp, false);
                 break;
             case Type::RRect:
-                canvas->clipRRect(mRRect, mOp);
+                // Ensure rounded rectangular clips are anti-aliased
+                canvas->clipRRect(mRRect, mOp, true);
                 break;
             case Type::Path:
-                canvas->clipPath(mPath.value(), mOp);
+                // Ensure path clips are anti-aliased
+                canvas->clipPath(mPath.value(), mOp, true);
                 break;
         }
     }
@@ -392,7 +395,7 @@
 
 bool SkiaCanvas::clipPath(const SkPath* path, SkClipOp op) {
     this->recordClip(*path, op);
-    mCanvas->clipPath(*path, op);
+    mCanvas->clipPath(*path, op, true);
     return !mCanvas->isClipEmpty();
 }
 
diff --git a/media/OWNERS b/media/OWNERS
index 8bd037a..be60583 100644
--- a/media/OWNERS
+++ b/media/OWNERS
@@ -5,6 +5,7 @@
 etalvala@google.com
 gkasten@google.com
 hdmoon@google.com
+hkuang@google.com
 hunga@google.com
 insun@google.com
 jaewan@google.com
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 6d37c2d..bc4da10 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -57,7 +57,7 @@
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     private static final Object sRouterLock = new Object();
 
-    @GuardedBy("sLock")
+    @GuardedBy("sRouterLock")
     private static MediaRouter2 sInstance;
 
     private final Context mContext;
@@ -73,23 +73,23 @@
             new CopyOnWriteArrayList<>();
 
     private final String mPackageName;
-    @GuardedBy("sLock")
+    @GuardedBy("sRouterLock")
     final Map<String, MediaRoute2Info> mRoutes = new HashMap<>();
 
-    @GuardedBy("sLock")
+    @GuardedBy("sRouterLock")
     private RouteDiscoveryPreference mDiscoveryPreference = RouteDiscoveryPreference.EMPTY;
 
     // TODO: Make MediaRouter2 is always connected to the MediaRouterService.
-    @GuardedBy("sLock")
+    @GuardedBy("sRouterLock")
     Client2 mClient;
 
-    @GuardedBy("sLock")
+    @GuardedBy("sRouterLock")
     private Map<String, RoutingController> mRoutingControllers = new ArrayMap<>();
 
     private AtomicInteger mSessionCreationRequestCnt = new AtomicInteger(1);
 
     final Handler mHandler;
-    @GuardedBy("sLock")
+    @GuardedBy("sRouterLock")
     private boolean mShouldUpdateRoutes;
     private volatile List<MediaRoute2Info> mFilteredRoutes = Collections.emptyList();
 
@@ -728,16 +728,15 @@
      * For example, selecting/deselcting/transferring routes to session can be done through this
      * class. Instances are created by {@link MediaRouter2}.
      *
-     * TODO: Need to add toString()
      * @hide
      */
     public final class RoutingController {
         private final Object mControllerLock = new Object();
 
-        @GuardedBy("mLock")
+        @GuardedBy("mControllerLock")
         private RoutingSessionInfo mSessionInfo;
 
-        @GuardedBy("mLock")
+        @GuardedBy("mControllerLock")
         private volatile boolean mIsReleased;
 
         RoutingController(@NonNull RoutingSessionInfo sessionInfo) {
@@ -998,6 +997,38 @@
             }
         }
 
+        @Override
+        public String toString() {
+            // To prevent logging spam, we only print the ID of each route.
+            List<String> selectedRoutes = getSelectedRoutes().stream()
+                    .map(MediaRoute2Info::getId).collect(Collectors.toList());
+            List<String> selectableRoutes = getSelectableRoutes().stream()
+                    .map(MediaRoute2Info::getId).collect(Collectors.toList());
+            List<String> deselectableRoutes = getDeselectableRoutes().stream()
+                    .map(MediaRoute2Info::getId).collect(Collectors.toList());
+            List<String> transferrableRoutes = getTransferrableRoutes().stream()
+                    .map(MediaRoute2Info::getId).collect(Collectors.toList());
+
+            StringBuilder result = new StringBuilder()
+                    .append("RoutingController{ ")
+                    .append("sessionId=").append(getSessionId())
+                    .append(", routeFeature=").append(getRouteFeature())
+                    .append(", selectedRoutes={")
+                    .append(selectedRoutes)
+                    .append("}")
+                    .append(", selectableRoutes={")
+                    .append(selectableRoutes)
+                    .append("}")
+                    .append(", deselectableRoutes={")
+                    .append(deselectableRoutes)
+                    .append("}")
+                    .append(", transferrableRoutes={")
+                    .append(transferrableRoutes)
+                    .append("}")
+                    .append(" }");
+            return result.toString();
+        }
+
         /**
          * TODO: Change this to package private. (Hidden for debugging purposes)
          * @hide
diff --git a/media/java/android/media/VolumeProvider.java b/media/java/android/media/VolumeProvider.java
index 8f68cbd..ed272d5 100644
--- a/media/java/android/media/VolumeProvider.java
+++ b/media/java/android/media/VolumeProvider.java
@@ -16,6 +16,7 @@
 package android.media;
 
 import android.annotation.IntDef;
+import android.annotation.Nullable;
 import android.media.session.MediaSession;
 
 import java.lang.annotation.Retention;
@@ -60,6 +61,7 @@
 
     private final int mControlType;
     private final int mMaxVolume;
+    private final String mControlId;
     private int mCurrentVolume;
     private Callback mCallback;
 
@@ -73,10 +75,28 @@
      * @param maxVolume The maximum allowed volume.
      * @param currentVolume The current volume on the output.
      */
+
     public VolumeProvider(@ControlType int volumeControl, int maxVolume, int currentVolume) {
+        this(volumeControl, maxVolume, currentVolume, null);
+    }
+
+    /**
+     * Create a new volume provider for handling volume events. You must specify
+     * the type of volume control, the maximum volume that can be used, and the
+     * current volume on the output.
+     *
+     * @param volumeControl The method for controlling volume that is used by
+     *            this provider.
+     * @param maxVolume The maximum allowed volume.
+     * @param currentVolume The current volume on the output.
+     * @param volumeControlId The volume control id of this provider.
+     */
+    public VolumeProvider(@ControlType int volumeControl, int maxVolume, int currentVolume,
+            @Nullable String volumeControlId) {
         mControlType = volumeControl;
         mMaxVolume = maxVolume;
         mCurrentVolume = currentVolume;
+        mControlId = volumeControlId;
     }
 
     /**
@@ -122,6 +142,17 @@
     }
 
     /**
+     * Gets the volume control id. It can be used to identify which volume provider is
+     * used by the session.
+     *
+     * @return the volume control id or {@code null} if it isn't set.
+     */
+    @Nullable
+    public final String getVolumeControlId() {
+        return mControlId;
+    }
+
+    /**
      * Override to handle requests to set the volume of the current output.
      * After the volume has been modified {@link #setCurrentVolume} must be
      * called to notify the system.
diff --git a/media/java/android/media/session/ISession.aidl b/media/java/android/media/session/ISession.aidl
index 4d68a6a..21378c8 100644
--- a/media/java/android/media/session/ISession.aidl
+++ b/media/java/android/media/session/ISession.aidl
@@ -48,6 +48,6 @@
 
     // These commands relate to volume handling
     void setPlaybackToLocal(in AudioAttributes attributes);
-    void setPlaybackToRemote(int control, int max);
+    void setPlaybackToRemote(int control, int max, @nullable String controlId);
     void setCurrentVolume(int currentVolume);
 }
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index 1812d9c..c2f6206 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -964,16 +964,26 @@
         private final int mMaxVolume;
         private final int mCurrentVolume;
         private final AudioAttributes mAudioAttrs;
+        private final String mVolumeControlId;
 
         /**
          * @hide
          */
         public PlaybackInfo(int type, int control, int max, int current, AudioAttributes attrs) {
+            this(type, control, max, current, attrs, null);
+        }
+
+        /**
+         * @hide
+         */
+        public PlaybackInfo(int type, int control, int max, int current, AudioAttributes attrs,
+                String volumeControlId) {
             mVolumeType = type;
             mVolumeControl = control;
             mMaxVolume = max;
             mCurrentVolume = current;
             mAudioAttrs = attrs;
+            mVolumeControlId = volumeControlId;
         }
 
         PlaybackInfo(Parcel in) {
@@ -982,6 +992,7 @@
             mMaxVolume = in.readInt();
             mCurrentVolume = in.readInt();
             mAudioAttrs = in.readParcelable(null);
+            mVolumeControlId = in.readString();
         }
 
         /**
@@ -1042,11 +1053,24 @@
             return mAudioAttrs;
         }
 
+        /**
+         * Gets the volume control ID for this session. It can be used to identify which
+         * volume provider is used by the session.
+         *
+         * @return the volume control ID for this session or {@code null} if it's local playback
+         * or not set.
+         * @see VolumeProvider#getVolumeControlId()
+         */
+        @Nullable
+        public String getVolumeControlId() {
+            return mVolumeControlId;
+        }
+
         @Override
         public String toString() {
             return "volumeType=" + mVolumeType + ", volumeControl=" + mVolumeControl
                     + ", maxVolume=" + mMaxVolume + ", currentVolume=" + mCurrentVolume
-                    + ", audioAttrs=" + mAudioAttrs;
+                    + ", audioAttrs=" + mAudioAttrs + ", volumeControlId=" + mVolumeControlId;
         }
 
         @Override
@@ -1061,6 +1085,7 @@
             dest.writeInt(mMaxVolume);
             dest.writeInt(mCurrentVolume);
             dest.writeParcelable(mAudioAttrs, flags);
+            dest.writeString(mVolumeControlId);
         }
 
         public static final @android.annotation.NonNull Parcelable.Creator<PlaybackInfo> CREATOR =
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 3adee59..9953626 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -335,7 +335,7 @@
 
         try {
             mBinder.setPlaybackToRemote(volumeProvider.getVolumeControl(),
-                    volumeProvider.getMaxVolume());
+                    volumeProvider.getMaxVolume(), volumeProvider.getVolumeControlId());
             mBinder.setCurrentVolume(volumeProvider.getCurrentVolume());
         } catch (RemoteException e) {
             Log.wtf(TAG, "Failure in setPlaybackToRemote.", e);
diff --git a/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl b/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl
index 149c1cd..726af76 100644
--- a/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl
+++ b/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl
@@ -44,4 +44,11 @@
      * and this event will be sent in addition to the abort event.
      */
     void onRecognitionAvailabilityChange(boolean available);
+    /**
+     * Notifies the client that the associated module has crashed and restarted. The module instance
+     * is no longer usable and will throw a ServiceSpecificException with a Status.DEAD_OBJECT code
+     * for every call. The client should detach, then re-attach to the module in order to get a new,
+     * usable instance. All state for this module has been lost.
+     */
+     void onModuleDied();
 }
diff --git a/media/java/android/media/soundtrigger_middleware/Status.aidl b/media/java/android/media/soundtrigger_middleware/Status.aidl
index 5d082e2..85ccacf 100644
--- a/media/java/android/media/soundtrigger_middleware/Status.aidl
+++ b/media/java/android/media/soundtrigger_middleware/Status.aidl
@@ -28,4 +28,6 @@
     OPERATION_NOT_SUPPORTED = 2,
     /** Temporary lack of permission. */
     TEMPORARY_PERMISSION_DENIED = 3,
+    /** The object on which this method is called is dead and all of its state is lost. */
+    DEAD_OBJECT = 4,
 }
diff --git a/media/java/android/media/tv/tuner/Lnb.java b/media/java/android/media/tv/tuner/Lnb.java
index c7cc9e6d..8e579bf 100644
--- a/media/java/android/media/tv/tuner/Lnb.java
+++ b/media/java/android/media/tv/tuner/Lnb.java
@@ -23,7 +23,6 @@
 import android.annotation.SystemApi;
 import android.content.Context;
 import android.hardware.tv.tuner.V1_0.Constants;
-import android.media.tv.tuner.Tuner.LnbCallback;
 import android.media.tv.tuner.TunerConstants.Result;
 
 import java.lang.annotation.Retention;
diff --git a/media/java/android/media/tv/tuner/LnbCallback.java b/media/java/android/media/tv/tuner/LnbCallback.java
new file mode 100644
index 0000000..99bbf86
--- /dev/null
+++ b/media/java/android/media/tv/tuner/LnbCallback.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.media.tv.tuner;
+
+
+/**
+ * Callback interface for receiving information from LNBs.
+ *
+ * @hide
+ */
+public interface LnbCallback {
+    /**
+     * Invoked when there is a LNB event.
+     */
+    void onEvent(int lnbEventType);
+
+    /**
+     * Invoked when there is a new DiSEqC message.
+     *
+     * @param diseqcMessage a byte array of data for DiSEqC (Digital Satellite
+     * Equipment Control) message which is specified by EUTELSAT Bus Functional
+     * Specification Version 4.2.
+     */
+    void onDiseqcMessage(byte[] diseqcMessage);
+}
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index e3dfaeb..6f5ba84 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -137,29 +137,10 @@
 
     private static native DemuxCapabilities nativeGetDemuxCapabilities();
 
-    /**
-     * LNB Callback.
-     *
-     * @hide
-     */
-    public interface LnbCallback {
-        /**
-         * Invoked when there is a LNB event.
-         */
-        void onEvent(int lnbEventType);
-
-        /**
-         * Invoked when there is a new DiSEqC message.
-         *
-         * @param diseqcMessage a byte array of data for DiSEqC (Digital Satellite
-         * Equipment Control) message which is specified by EUTELSAT Bus Functional
-         * Specification Version 4.2.
-         */
-        void onDiseqcMessage(byte[] diseqcMessage);
-    }
 
     /**
      * Callback interface for receiving information from the corresponding filters.
+     * TODO: remove
      */
     public interface FilterCallback {
         /**
@@ -178,22 +159,6 @@
         void onFilterStatusChanged(@NonNull Filter filter, @FilterStatus int status);
     }
 
-    /**
-     * DVR Callback.
-     *
-     * @hide
-     */
-    public interface DvrCallback {
-        /**
-         * Invoked when record status changed.
-         */
-        void onRecordStatus(int status);
-        /**
-         * Invoked when playback status changed.
-         */
-        void onPlaybackStatus(int status);
-    }
-
     @Nullable
     private EventHandler createEventHandler() {
         Looper looper;
diff --git a/media/java/android/media/tv/tuner/TunerConstants.java b/media/java/android/media/tv/tuner/TunerConstants.java
index 4532122..42229a0 100644
--- a/media/java/android/media/tv/tuner/TunerConstants.java
+++ b/media/java/android/media/tv/tuner/TunerConstants.java
@@ -124,6 +124,34 @@
      */
     public static final int FILTER_STATUS_OVERFLOW = Constants.DemuxFilterStatus.OVERFLOW;
 
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "INDEX_TYPE_", value =
+            {INDEX_TYPE_NONE, INDEX_TYPE_TS, INDEX_TYPE_SC, INDEX_TYPE_SC_HEVC})
+    public @interface IndexType {}
+
+    /**
+     * Index is not used.
+     * @hide
+     */
+    public static final int INDEX_TYPE_NONE = Constants.DemuxRecordIndexType.NONE;
+    /**
+     * TS index.
+     * @hide
+     */
+    public static final int INDEX_TYPE_TS = Constants.DemuxRecordIndexType.TS;
+    /**
+     * Start Code index.
+     * @hide
+     */
+    public static final int INDEX_TYPE_SC = Constants.DemuxRecordIndexType.SC;
+    /**
+     * Start Code index for HEVC.
+     * @hide
+     */
+    public static final int INDEX_TYPE_SC_HEVC = Constants.DemuxRecordIndexType.SC_HEVC;
+
     /**
      * Indexes can be tagged through TS (Transport Stream) header.
      *
diff --git a/media/java/android/media/tv/tuner/dvr/Dvr.java b/media/java/android/media/tv/tuner/dvr/Dvr.java
index f90042b..95508d3 100644
--- a/media/java/android/media/tv/tuner/dvr/Dvr.java
+++ b/media/java/android/media/tv/tuner/dvr/Dvr.java
@@ -18,7 +18,6 @@
 
 import android.annotation.BytesLong;
 import android.annotation.NonNull;
-import android.media.tv.tuner.Tuner.DvrCallback;
 import android.media.tv.tuner.Tuner.Filter;
 import android.media.tv.tuner.TunerConstants.Result;
 import android.os.ParcelFileDescriptor;
diff --git a/media/java/android/media/tv/tuner/dvr/DvrCallback.java b/media/java/android/media/tv/tuner/dvr/DvrCallback.java
new file mode 100644
index 0000000..3d140f0
--- /dev/null
+++ b/media/java/android/media/tv/tuner/dvr/DvrCallback.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.media.tv.tuner.dvr;
+
+/**
+ * Callback interface for receiving information from DVR interfaces.
+ *
+ * @hide
+ */
+public interface DvrCallback {
+    /**
+     * Invoked when record status changed.
+     */
+    void onRecordStatusChanged(int status);
+    /**
+     * Invoked when playback status changed.
+     */
+    void onPlaybackStatusChanged(int status);
+}
diff --git a/media/java/android/media/tv/tuner/filter/AlpFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/AlpFilterConfiguration.java
index f0fe533..fcca6a1 100644
--- a/media/java/android/media/tv/tuner/filter/AlpFilterConfiguration.java
+++ b/media/java/android/media/tv/tuner/filter/AlpFilterConfiguration.java
@@ -16,20 +16,123 @@
 
 package android.media.tv.tuner.filter;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Filter configuration for a ALP filter.
  * @hide
  */
 public class AlpFilterConfiguration extends FilterConfiguration {
-    private int mPacketType;
-    private int mLengthType;
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "LENGTH_TYPE_", value =
+            {LENGTH_TYPE_UNDEFINED, LENGTH_TYPE_WITHOUT_ADDITIONAL_HEADER,
+            LENGTH_TYPE_WITH_ADDITIONAL_HEADER})
+    public @interface LengthType {}
 
-    public AlpFilterConfiguration(Settings settings) {
+    /**
+     * Length type not defined.
+     */
+    public static final int LENGTH_TYPE_UNDEFINED = Constants.DemuxAlpLengthType.UNDEFINED;
+    /**
+     * Length does NOT include additional header.
+     */
+    public static final int LENGTH_TYPE_WITHOUT_ADDITIONAL_HEADER =
+            Constants.DemuxAlpLengthType.WITHOUT_ADDITIONAL_HEADER;
+    /**
+     * Length includes additional header.
+     */
+    public static final int LENGTH_TYPE_WITH_ADDITIONAL_HEADER =
+            Constants.DemuxAlpLengthType.WITH_ADDITIONAL_HEADER;
+
+
+    private final int mPacketType;
+    private final int mLengthType;
+
+    public AlpFilterConfiguration(Settings settings, int packetType, int lengthType) {
         super(settings);
+        mPacketType = packetType;
+        mLengthType = lengthType;
     }
 
     @Override
     public int getType() {
         return FilterConfiguration.FILTER_TYPE_ALP;
     }
+
+    /**
+     * Gets packet type.
+     */
+    @FilterConfiguration.PacketType
+    public int getPacketType() {
+        return mPacketType;
+    }
+    /**
+     * Gets length type.
+     */
+    @LengthType
+    public int getLengthType() {
+        return mLengthType;
+    }
+
+    /**
+     * Creates a builder for {@link AlpFilterConfiguration}.
+     *
+     * @param context the context of the caller.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @NonNull
+    public static Builder builder(@NonNull Context context) {
+        TunerUtils.checkTunerPermission(context);
+        return new Builder();
+    }
+
+    /**
+     * Builder for {@link AlpFilterConfiguration}.
+     */
+    public static class Builder extends FilterConfiguration.Builder<Builder> {
+        private int mPacketType;
+        private int mLengthType;
+
+        private Builder() {
+        }
+
+        /**
+         * Sets packet type.
+         */
+        @NonNull
+        public Builder setPacketType(@FilterConfiguration.PacketType int packetType) {
+            mPacketType = packetType;
+            return this;
+        }
+        /**
+         * Sets length type.
+         */
+        @NonNull
+        public Builder setLengthType(@LengthType int lengthType) {
+            mLengthType = lengthType;
+            return this;
+        }
+
+        /**
+         * Builds a {@link AlpFilterConfiguration} object.
+         */
+        @NonNull
+        public AlpFilterConfiguration build() {
+            return new AlpFilterConfiguration(mSettings, mPacketType, mLengthType);
+        }
+
+        @Override
+        Builder self() {
+            return this;
+        }
+    }
 }
diff --git a/media/java/android/media/tv/tuner/filter/AvSettings.java b/media/java/android/media/tv/tuner/filter/AvSettings.java
index a7c49d5..940b5ae 100644
--- a/media/java/android/media/tv/tuner/filter/AvSettings.java
+++ b/media/java/android/media/tv/tuner/filter/AvSettings.java
@@ -16,21 +16,84 @@
 
 package android.media.tv.tuner.filter;
 
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
 import android.media.tv.tuner.TunerConstants;
 import android.media.tv.tuner.TunerUtils;
+import android.media.tv.tuner.filter.FilterConfiguration.FilterType;
 
 /**
  * Filter Settings for a Video and Audio.
+ *
  * @hide
  */
 public class AvSettings extends Settings {
-    private boolean mIsPassthrough;
+    private final boolean mIsPassthrough;
 
-    private AvSettings(int mainType, boolean isAudio) {
+    private AvSettings(int mainType, boolean isAudio, boolean isPassthrough) {
         super(TunerUtils.getFilterSubtype(
                 mainType,
                 isAudio
                         ? TunerConstants.FILTER_SUBTYPE_AUDIO
                         : TunerConstants.FILTER_SUBTYPE_VIDEO));
+        mIsPassthrough = isPassthrough;
+    }
+
+    /**
+     * Checks whether it's passthrough.
+     */
+    public boolean isPassthrough() {
+        return mIsPassthrough;
+    }
+
+    /**
+     * Creates a builder for {@link AvSettings}.
+     *
+     * @param context the context of the caller.
+     * @param mainType the filter main type.
+     * @param isAudio {@code true} if it's audio settings; {@code false} if it's video settings.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @NonNull
+    public static Builder builder(
+            @NonNull Context context, @FilterType int mainType, boolean isAudio) {
+        TunerUtils.checkTunerPermission(context);
+        return new Builder(mainType, isAudio);
+    }
+
+    /**
+     * Builder for {@link AvSettings}.
+     */
+    public static class Builder extends Settings.Builder<Builder> {
+        private final boolean mIsAudio;
+        private boolean mIsPassthrough;
+
+        private Builder(int mainType, boolean isAudio) {
+            super(mainType);
+            mIsAudio = isAudio;
+        }
+
+        /**
+         * Sets whether it's passthrough.
+         */
+        @NonNull
+        public Builder setPassthrough(boolean isPassthrough) {
+            mIsPassthrough = isPassthrough;
+            return this;
+        }
+
+        /**
+         * Builds a {@link AvSettings} object.
+         */
+        @NonNull
+        public AvSettings build() {
+            return new AvSettings(mMainType, mIsAudio, mIsPassthrough);
+        }
+
+        @Override
+        Builder self() {
+            return this;
+        }
     }
 }
diff --git a/media/java/android/media/tv/tuner/filter/DownloadSettings.java b/media/java/android/media/tv/tuner/filter/DownloadSettings.java
index 0742b11..e3e1df0 100644
--- a/media/java/android/media/tv/tuner/filter/DownloadSettings.java
+++ b/media/java/android/media/tv/tuner/filter/DownloadSettings.java
@@ -16,17 +16,75 @@
 
 package android.media.tv.tuner.filter;
 
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
 import android.media.tv.tuner.TunerConstants;
 import android.media.tv.tuner.TunerUtils;
+import android.media.tv.tuner.filter.FilterConfiguration.FilterType;
 
 /**
  * Filter Settings for a Download.
  * @hide
  */
 public class DownloadSettings extends Settings {
-    private int mDownloadId;
+    private final int mDownloadId;
 
-    public DownloadSettings(int mainType) {
+    private DownloadSettings(int mainType, int downloadId) {
         super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_DOWNLOAD));
+        mDownloadId = downloadId;
+    }
+
+    /**
+     * Gets download ID.
+     */
+    public int getDownloadId() {
+        return mDownloadId;
+    }
+
+    /**
+     * Creates a builder for {@link DownloadSettings}.
+     *
+     * @param context the context of the caller.
+     * @param mainType the filter main type.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @NonNull
+    public static Builder builder(@NonNull Context context, @FilterType int mainType) {
+        TunerUtils.checkTunerPermission(context);
+        return new Builder(mainType);
+    }
+
+    /**
+     * Builder for {@link DownloadSettings}.
+     */
+    public static class Builder extends Settings.Builder<Builder> {
+        private int mDownloadId;
+
+        private Builder(int mainType) {
+            super(mainType);
+        }
+
+        /**
+         * Sets download ID.
+         */
+        @NonNull
+        public Builder setDownloadId(int downloadId) {
+            mDownloadId = downloadId;
+            return this;
+        }
+
+        /**
+         * Builds a {@link DownloadSettings} object.
+         */
+        @NonNull
+        public DownloadSettings build() {
+            return new DownloadSettings(mMainType, mDownloadId);
+        }
+
+        @Override
+        Builder self() {
+            return this;
+        }
     }
 }
diff --git a/media/java/android/media/tv/tuner/filter/FilterCallback.java b/media/java/android/media/tv/tuner/filter/FilterCallback.java
new file mode 100644
index 0000000..888adc5
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/FilterCallback.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.media.tv.tuner.filter;
+
+import android.annotation.NonNull;
+import android.media.tv.tuner.TunerConstants.FilterStatus;
+
+/**
+ * Callback interface for receiving information from the corresponding filters.
+ *
+ * @hide
+ */
+public interface FilterCallback {
+    /**
+     * Invoked when there are filter events.
+     *
+     * @param filter the corresponding filter which sent the events.
+     * @param events the filter events sent from the filter.
+     */
+    void onFilterEvent(@NonNull Filter filter, @NonNull FilterEvent[] events);
+    /**
+     * Invoked when filter status changed.
+     *
+     * @param filter the corresponding filter whose status is changed.
+     * @param status the new status of the filter.
+     */
+    void onFilterStatusChanged(@NonNull Filter filter, @FilterStatus int status);
+}
diff --git a/media/java/android/media/tv/tuner/filter/FilterConfiguration.java b/media/java/android/media/tv/tuner/filter/FilterConfiguration.java
index 6496627..68c722f 100644
--- a/media/java/android/media/tv/tuner/filter/FilterConfiguration.java
+++ b/media/java/android/media/tv/tuner/filter/FilterConfiguration.java
@@ -33,7 +33,8 @@
 public abstract class FilterConfiguration {
 
     /** @hide */
-    @IntDef({FILTER_TYPE_TS, FILTER_TYPE_MMTP, FILTER_TYPE_IP, FILTER_TYPE_TLV, FILTER_TYPE_ALP})
+    @IntDef(prefix = "FILTER_TYPE_", value =
+            {FILTER_TYPE_TS, FILTER_TYPE_MMTP, FILTER_TYPE_IP, FILTER_TYPE_TLV, FILTER_TYPE_ALP})
     @Retention(RetentionPolicy.SOURCE)
     public @interface FilterType {}
 
@@ -58,6 +59,30 @@
      */
     public static final int FILTER_TYPE_ALP = Constants.DemuxFilterMainType.ALP;
 
+
+    /** @hide */
+    @IntDef(prefix = "PACKET_TYPE_", value =
+            {PACKET_TYPE_IPV4, PACKET_TYPE_COMPRESSED, PACKET_TYPE_SIGNALING})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PacketType {}
+
+    /**
+     * IP v4 packet type.
+     * @hide
+     */
+    public static final int PACKET_TYPE_IPV4 = 0;
+    /**
+     * Compressed packet type.
+     * @hide
+     */
+    public static final int PACKET_TYPE_COMPRESSED = 2;
+    /**
+     * Signaling packet type.
+     * @hide
+     */
+    public static final int PACKET_TYPE_SIGNALING = 4;
+
+
     @Nullable
     /* package */ final Settings mSettings;
 
@@ -77,4 +102,27 @@
     public Settings getSettings() {
         return mSettings;
     }
+
+    /**
+     * Builder for {@link FilterConfiguration}.
+     *
+     * @param <T> The subclass to be built.
+     * @hide
+     */
+    public abstract static class Builder<T extends Builder<T>> {
+        /* package */ Settings mSettings;
+
+        /* package */ Builder() {
+        }
+
+        /**
+         * Sets filter settings.
+         */
+        @Nullable
+        public T setFrequency(Settings settings) {
+            mSettings = settings;
+            return self();
+        }
+        /* package */ abstract T self();
+    }
 }
diff --git a/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java
index c896368..98edf10 100644
--- a/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java
+++ b/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java
@@ -16,23 +16,152 @@
 
 package android.media.tv.tuner.filter;
 
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.Size;
+import android.content.Context;
+import android.media.tv.tuner.TunerUtils;
+
 /**
  * Filter configuration for a IP filter.
  * @hide
  */
 public class IpFilterConfiguration extends FilterConfiguration {
-    private byte[] mSrcIpAddress;
-    private byte[] mDstIpAddress;
-    private int mSrcPort;
-    private int mDstPort;
-    private boolean mPassthrough;
+    private final byte[] mSrcIpAddress;
+    private final byte[] mDstIpAddress;
+    private final int mSrcPort;
+    private final int mDstPort;
+    private final boolean mPassthrough;
 
-    public IpFilterConfiguration(Settings settings) {
+    public IpFilterConfiguration(Settings settings, byte[] srcAddr, byte[] dstAddr, int srcPort,
+            int dstPort, boolean passthrough) {
         super(settings);
+        mSrcIpAddress = srcAddr;
+        mDstIpAddress = dstAddr;
+        mSrcPort = srcPort;
+        mDstPort = dstPort;
+        mPassthrough = passthrough;
     }
 
     @Override
     public int getType() {
         return FilterConfiguration.FILTER_TYPE_IP;
     }
+
+    /**
+     * Gets source IP address.
+     */
+    @Size(min = 4, max = 16)
+    public byte[] getSrcIpAddress() {
+        return mSrcIpAddress;
+    }
+    /**
+     * Gets destination IP address.
+     */
+    @Size(min = 4, max = 16)
+    public byte[] getDstIpAddress() {
+        return mDstIpAddress;
+    }
+    /**
+     * Gets source port.
+     */
+    public int getSrcPort() {
+        return mSrcPort;
+    }
+    /**
+     * Gets destination port.
+     */
+    public int getDstPort() {
+        return mDstPort;
+    }
+    /**
+     * Checks whether the filter is passthrough.
+     *
+     * @return {@code true} if the data from IP subtype go to next filter directly;
+     *         {@code false} otherwise.
+     */
+    public boolean isPassthrough() {
+        return mPassthrough;
+    }
+
+    /**
+     * Creates a builder for {@link IpFilterConfiguration}.
+     *
+     * @param context the context of the caller.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @NonNull
+    public static Builder builder(@NonNull Context context) {
+        TunerUtils.checkTunerPermission(context);
+        return new Builder();
+    }
+
+    /**
+     * Builder for {@link IpFilterConfiguration}.
+     */
+    public static class Builder extends FilterConfiguration.Builder<Builder> {
+        private byte[] mSrcIpAddress;
+        private byte[] mDstIpAddress;
+        private int mSrcPort;
+        private int mDstPort;
+        private boolean mPassthrough;
+
+        private Builder() {
+        }
+
+        /**
+         * Sets source IP address.
+         */
+        @NonNull
+        public Builder setSrcIpAddress(byte[] srcIpAddress) {
+            mSrcIpAddress = srcIpAddress;
+            return this;
+        }
+        /**
+         * Sets destination IP address.
+         */
+        @NonNull
+        public Builder setDstIpAddress(byte[] dstIpAddress) {
+            mDstIpAddress = dstIpAddress;
+            return this;
+        }
+        /**
+         * Sets source port.
+         */
+        @NonNull
+        public Builder setSrcPort(int srcPort) {
+            mSrcPort = srcPort;
+            return this;
+        }
+        /**
+         * Sets destination port.
+         */
+        @NonNull
+        public Builder setDstPort(int dstPort) {
+            mDstPort = dstPort;
+            return this;
+        }
+        /**
+         * Sets passthrough.
+         */
+        @NonNull
+        public Builder setPassthrough(boolean passthrough) {
+            mPassthrough = passthrough;
+            return this;
+        }
+
+        /**
+         * Builds a {@link IpFilterConfiguration} object.
+         */
+        @NonNull
+        public IpFilterConfiguration build() {
+            return new IpFilterConfiguration(
+                    mSettings, mSrcIpAddress, mDstIpAddress, mSrcPort, mDstPort, mPassthrough);
+        }
+
+        @Override
+        Builder self() {
+            return this;
+        }
+    }
 }
diff --git a/media/java/android/media/tv/tuner/filter/MmtpFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/MmtpFilterConfiguration.java
index 9045ce6..83246e5 100644
--- a/media/java/android/media/tv/tuner/filter/MmtpFilterConfiguration.java
+++ b/media/java/android/media/tv/tuner/filter/MmtpFilterConfiguration.java
@@ -16,19 +16,78 @@
 
 package android.media.tv.tuner.filter;
 
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.media.tv.tuner.TunerUtils;
+
 /**
  * Filter configuration for a MMTP filter.
  * @hide
  */
 public class MmtpFilterConfiguration extends FilterConfiguration {
-    private int mMmtpPid;
+    private final int mMmtpPid;
 
-    public MmtpFilterConfiguration(Settings settings) {
+    public MmtpFilterConfiguration(Settings settings, int mmtpPid) {
         super(settings);
+        mMmtpPid = mmtpPid;
     }
 
     @Override
     public int getType() {
         return FilterConfiguration.FILTER_TYPE_MMTP;
     }
+
+    /**
+     * Gets MMPT PID.
+     *
+     * <p>Packet ID is used to specify packets in MMTP.
+     */
+    public int getMmtpPid() {
+        return mMmtpPid;
+    }
+
+    /**
+     * Creates a builder for {@link IpFilterConfiguration}.
+     *
+     * @param context the context of the caller.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @NonNull
+    public static Builder builder(@NonNull Context context) {
+        TunerUtils.checkTunerPermission(context);
+        return new Builder();
+    }
+
+    /**
+     * Builder for {@link IpFilterConfiguration}.
+     */
+    public static class Builder extends FilterConfiguration.Builder<Builder> {
+        private int mMmtpPid;
+
+        private Builder() {
+        }
+
+        /**
+         * Sets MMPT PID.
+         */
+        @NonNull
+        public Builder setMmtpPid(int mmtpPid) {
+            mMmtpPid = mmtpPid;
+            return this;
+        }
+
+        /**
+         * Builds a {@link IpFilterConfiguration} object.
+         */
+        @NonNull
+        public MmtpFilterConfiguration build() {
+            return new MmtpFilterConfiguration(mSettings, mMmtpPid);
+        }
+
+        @Override
+        Builder self() {
+            return this;
+        }
+    }
 }
diff --git a/media/java/android/media/tv/tuner/filter/RecordSettings.java b/media/java/android/media/tv/tuner/filter/RecordSettings.java
index 701868a..2930f2d 100644
--- a/media/java/android/media/tv/tuner/filter/RecordSettings.java
+++ b/media/java/android/media/tv/tuner/filter/RecordSettings.java
@@ -16,18 +16,96 @@
 
 package android.media.tv.tuner.filter;
 
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
 import android.media.tv.tuner.TunerConstants;
+import android.media.tv.tuner.TunerConstants.IndexType;
 import android.media.tv.tuner.TunerUtils;
+import android.media.tv.tuner.filter.FilterConfiguration.FilterType;
 
 /**
  * The Settings for the record in DVR.
  * @hide
  */
 public class RecordSettings extends Settings {
-    private int mIndexType;
-    private int mIndexMask;
+    private final int mIndexType;
+    private final int mIndexMask;
 
-    public RecordSettings(int mainType) {
+    private RecordSettings(int mainType, int indexType, int indexMask) {
         super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_RECORD));
+        mIndexType = indexType;
+        mIndexMask = indexMask;
     }
+
+    /**
+     * Gets index type.
+     */
+    @IndexType
+    public int getIndexType() {
+        return mIndexType;
+    }
+    /**
+     * Gets index mask.
+     */
+    @TsRecordEvent.IndexMask
+    public int getIndexMask() {
+        return mIndexMask;
+    }
+
+    /**
+     * Creates a builder for {@link RecordSettings}.
+     *
+     * @param context the context of the caller.
+     * @param mainType the filter main type.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @NonNull
+    public static Builder builder(@NonNull Context context, @FilterType int mainType) {
+        TunerUtils.checkTunerPermission(context);
+        return new Builder(mainType);
+    }
+
+    /**
+     * Builder for {@link RecordSettings}.
+     */
+    public static class Builder extends Settings.Builder<Builder> {
+        private int mIndexType;
+        private int mIndexMask;
+
+        private Builder(int mainType) {
+            super(mainType);
+        }
+
+        /**
+         * Sets index type.
+         */
+        @NonNull
+        public Builder setIndexType(@IndexType int indexType) {
+            mIndexType = indexType;
+            return this;
+        }
+        /**
+         * Sets index mask.
+         */
+        @NonNull
+        public Builder setIndexMask(@TsRecordEvent.IndexMask int indexMask) {
+            mIndexMask = indexMask;
+            return this;
+        }
+
+        /**
+         * Builds a {@link RecordSettings} object.
+         */
+        @NonNull
+        public RecordSettings build() {
+            return new RecordSettings(mMainType, mIndexType, mIndexMask);
+        }
+
+        @Override
+        Builder self() {
+            return this;
+        }
+    }
+
 }
diff --git a/media/java/android/media/tv/tuner/filter/SectionSettingsWithSectionBits.java b/media/java/android/media/tv/tuner/filter/SectionSettingsWithSectionBits.java
index 414ea67..0fa982e 100644
--- a/media/java/android/media/tv/tuner/filter/SectionSettingsWithSectionBits.java
+++ b/media/java/android/media/tv/tuner/filter/SectionSettingsWithSectionBits.java
@@ -16,18 +16,116 @@
 
 package android.media.tv.tuner.filter;
 
-import java.util.List;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.media.tv.tuner.TunerUtils;
+import android.media.tv.tuner.filter.FilterConfiguration.FilterType;
 
 /**
- * Bits Settings for Section Filter.
+ * Bits Settings for Section Filters.
  * @hide
  */
 public class SectionSettingsWithSectionBits extends SectionSettings {
-    private List<Byte> mFilter;
-    private List<Byte> mMask;
-    private List<Byte> mMode;
+    private final byte[] mFilter;
+    private final byte[] mMask;
+    private final byte[] mMode;
 
-    private SectionSettingsWithSectionBits(int mainType) {
+
+    private SectionSettingsWithSectionBits(int mainType, byte[] filter, byte[] mask, byte[] mode) {
         super(mainType);
+        mFilter = filter;
+        mMask = mask;
+        mMode = mode;
+    }
+
+    /**
+     * Gets the bytes configured for Section Filter
+     */
+    public byte[] getFilterBytes() {
+        return mFilter;
+    }
+    /**
+     * Gets bit mask.
+     *
+     * <p>The bits in the bytes are used for filtering.
+     */
+    public byte[] getMask() {
+        return mMask;
+    }
+    /**
+     * Gets mode.
+     *
+     * <p>Do positive match at the bit position of the configured bytes when the bit at same
+     * position of the mode is 0.
+     * <p>Do negative match at the bit position of the configured bytes when the bit at same
+     * position of the mode is 1.
+     */
+    public byte[] getMode() {
+        return mMode;
+    }
+
+    /**
+     * Creates a builder for {@link SectionSettingsWithSectionBits}.
+     *
+     * @param context the context of the caller.
+     * @param mainType the filter main type.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @NonNull
+    public static Builder builder(@NonNull Context context, @FilterType int mainType) {
+        TunerUtils.checkTunerPermission(context);
+        return new Builder(mainType);
+    }
+
+    /**
+     * Builder for {@link SectionSettingsWithSectionBits}.
+     */
+    public static class Builder extends Settings.Builder<Builder> {
+        private byte[] mFilter;
+        private byte[] mMask;
+        private byte[] mMode;
+
+        private Builder(int mainType) {
+            super(mainType);
+        }
+
+        /**
+         * Sets filter bytes.
+         */
+        @NonNull
+        public Builder setFilter(byte[] filter) {
+            mFilter = filter;
+            return this;
+        }
+        /**
+         * Sets bit mask.
+         */
+        @NonNull
+        public Builder setMask(byte[] mask) {
+            mMask = mask;
+            return this;
+        }
+        /**
+         * Sets mode.
+         */
+        @NonNull
+        public Builder setMode(byte[] mode) {
+            mMode = mode;
+            return this;
+        }
+
+        /**
+         * Builds a {@link SectionSettingsWithSectionBits} object.
+         */
+        @NonNull
+        public SectionSettingsWithSectionBits build() {
+            return new SectionSettingsWithSectionBits(mMainType, mFilter, mMask, mMode);
+        }
+
+        @Override
+        Builder self() {
+            return this;
+        }
     }
 }
diff --git a/media/java/android/media/tv/tuner/filter/SectionSettingsWithTableInfo.java b/media/java/android/media/tv/tuner/filter/SectionSettingsWithTableInfo.java
index 0df1d73..6542b89 100644
--- a/media/java/android/media/tv/tuner/filter/SectionSettingsWithTableInfo.java
+++ b/media/java/android/media/tv/tuner/filter/SectionSettingsWithTableInfo.java
@@ -16,15 +16,92 @@
 
 package android.media.tv.tuner.filter;
 
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.media.tv.tuner.TunerUtils;
+import android.media.tv.tuner.filter.FilterConfiguration.FilterType;
+
 /**
  * Table information for Section Filter.
  * @hide
  */
 public class SectionSettingsWithTableInfo extends SectionSettings {
-    private int mTableId;
-    private int mVersion;
+    private final int mTableId;
+    private final int mVersion;
 
-    private SectionSettingsWithTableInfo(int mainType) {
+    private SectionSettingsWithTableInfo(int mainType, int tableId, int version) {
         super(mainType);
+        mTableId = tableId;
+        mVersion = version;
     }
+
+    /**
+     * Gets table ID.
+     */
+    public int getTableId() {
+        return mTableId;
+    }
+    /**
+     * Gets version.
+     */
+    public int getVersion() {
+        return mVersion;
+    }
+
+    /**
+     * Creates a builder for {@link SectionSettingsWithTableInfo}.
+     *
+     * @param context the context of the caller.
+     * @param mainType the filter main type.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @NonNull
+    public static Builder builder(@NonNull Context context, @FilterType int mainType) {
+        TunerUtils.checkTunerPermission(context);
+        return new Builder(mainType);
+    }
+
+    /**
+     * Builder for {@link SectionSettingsWithTableInfo}.
+     */
+    public static class Builder extends Settings.Builder<Builder> {
+        private int mTableId;
+        private int mVersion;
+
+        private Builder(int mainType) {
+            super(mainType);
+        }
+
+        /**
+         * Sets table ID.
+         */
+        @NonNull
+        public Builder setTableId(int tableId) {
+            mTableId = tableId;
+            return this;
+        }
+        /**
+         * Sets version.
+         */
+        @NonNull
+        public Builder setVersion(int version) {
+            mVersion = version;
+            return this;
+        }
+
+        /**
+         * Builds a {@link SectionSettingsWithTableInfo} object.
+         */
+        @NonNull
+        public SectionSettingsWithTableInfo build() {
+            return new SectionSettingsWithTableInfo(mMainType, mTableId, mVersion);
+        }
+
+        @Override
+        Builder self() {
+            return this;
+        }
+    }
+
 }
diff --git a/media/java/android/media/tv/tuner/filter/Settings.java b/media/java/android/media/tv/tuner/filter/Settings.java
index 9155926..d697280 100644
--- a/media/java/android/media/tv/tuner/filter/Settings.java
+++ b/media/java/android/media/tv/tuner/filter/Settings.java
@@ -39,4 +39,20 @@
     public int getType() {
         return mType;
     }
+
+
+    /**
+     * Builder for {@link Settings}.
+     *
+     * @param <T> The subclass to be built.
+     * @hide
+     */
+    public abstract static class Builder<T extends Builder<T>> {
+        /* package */ final int mMainType;
+
+        /* package */ Builder(int mainType) {
+            mMainType = mainType;
+        }
+        /* package */ abstract T self();
+    }
 }
diff --git a/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java
index de8ee75..eb97fc0 100644
--- a/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java
+++ b/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java
@@ -16,21 +16,118 @@
 
 package android.media.tv.tuner.filter;
 
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.media.tv.tuner.TunerUtils;
+
 /**
  * Filter configuration for a TLV filter.
  * @hide
  */
 public class TlvFilterConfiguration extends FilterConfiguration {
-    private int mPacketType;
-    private boolean mIsCompressedIpPacket;
-    private boolean mPassthrough;
+    private final int mPacketType;
+    private final boolean mIsCompressedIpPacket;
+    private final boolean mPassthrough;
 
-    public TlvFilterConfiguration(Settings settings) {
+    public TlvFilterConfiguration(Settings settings, int packetType, boolean isCompressed,
+            boolean passthrough) {
         super(settings);
+        mPacketType = packetType;
+        mIsCompressedIpPacket = isCompressed;
+        mPassthrough = passthrough;
     }
 
     @Override
     public int getType() {
         return FilterConfiguration.FILTER_TYPE_TLV;
     }
+
+    /**
+     * Gets packet type.
+     */
+    @FilterConfiguration.PacketType
+    public int getPacketType() {
+        return mPacketType;
+    }
+    /**
+     * Checks whether the data is compressed IP packet.
+     *
+     * @return {@code true} if the filtered data is compressed IP packet; {@code false} otherwise.
+     */
+    public boolean isCompressedIpPacket() {
+        return mIsCompressedIpPacket;
+    }
+    /**
+     * Checks whether it's passthrough.
+     *
+     * @return {@code true} if the data from TLV subtype go to next filter directly;
+     *         {@code false} otherwise.
+     */
+    public boolean isPassthrough() {
+        return mPassthrough;
+    }
+
+    /**
+     * Creates a builder for {@link TlvFilterConfiguration}.
+     *
+     * @param context the context of the caller.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @NonNull
+    public static Builder builder(@NonNull Context context) {
+        TunerUtils.checkTunerPermission(context);
+        return new Builder();
+    }
+
+    /**
+     * Builder for {@link TlvFilterConfiguration}.
+     */
+    public static class Builder extends FilterConfiguration.Builder<Builder> {
+        private int mPacketType;
+        private boolean mIsCompressedIpPacket;
+        private boolean mPassthrough;
+
+        private Builder() {
+        }
+
+        /**
+         * Sets packet type.
+         */
+        @NonNull
+        public Builder setPacketType(@FilterConfiguration.PacketType int packetType) {
+            mPacketType = packetType;
+            return this;
+        }
+        /**
+         * Sets whether the data is compressed IP packet.
+         */
+        @NonNull
+        public Builder setIsCompressedIpPacket(boolean isCompressedIpPacket) {
+            mIsCompressedIpPacket = isCompressedIpPacket;
+            return this;
+        }
+        /**
+         * Sets whether it's passthrough.
+         */
+        @NonNull
+        public Builder setPassthrough(boolean passthrough) {
+            mPassthrough = passthrough;
+            return this;
+        }
+
+        /**
+         * Builds a {@link TlvFilterConfiguration} object.
+         */
+        @NonNull
+        public TlvFilterConfiguration build() {
+            return new TlvFilterConfiguration(
+                    mSettings, mPacketType, mIsCompressedIpPacket, mPassthrough);
+        }
+
+        @Override
+        Builder self() {
+            return this;
+        }
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 747ceb1..50d3a5d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -262,13 +262,28 @@
         }
     }
 
+    /**
+     * Connect this device.
+     *
+     * @param connectAllProfiles {@code true} to connect all profile, {@code false} otherwise.
+     *
+     * @deprecated use {@link #connect()} instead.
+     */
+    @Deprecated
     public void connect(boolean connectAllProfiles) {
+        connect();
+    }
+
+    /**
+     * Connect this device.
+     */
+    public void connect() {
         if (!ensurePaired()) {
             return;
         }
 
         mConnectAttempted = SystemClock.elapsedRealtime();
-        connectWithoutResettingTimer(connectAllProfiles);
+        connectAllEnabledProfiles();
     }
 
     public long getHiSyncId() {
@@ -289,10 +304,10 @@
     void onBondingDockConnect() {
         // Attempt to connect if UUIDs are available. Otherwise,
         // we will connect when the ACTION_UUID intent arrives.
-        connect(false);
+        connect();
     }
 
-    private void connectWithoutResettingTimer(boolean connectAllProfiles) {
+    private void connectAllEnabledProfiles() {
         synchronized (mProfileLock) {
             // Try to initialize the profiles if they were not.
             if (mProfiles.isEmpty()) {
@@ -307,36 +322,7 @@
                 return;
             }
 
-            int preferredProfiles = 0;
-            for (LocalBluetoothProfile profile : mProfiles) {
-                if (connectAllProfiles ? profile.accessProfileEnabled()
-                        : profile.isAutoConnectable()) {
-                    if (profile.isPreferred(mDevice)) {
-                        ++preferredProfiles;
-                        connectInt(profile);
-                    }
-                }
-            }
-            if (BluetoothUtils.D) Log.d(TAG, "Preferred profiles = " + preferredProfiles);
-
-            if (preferredProfiles == 0) {
-                connectAutoConnectableProfiles();
-            }
-        }
-    }
-
-    private void connectAutoConnectableProfiles() {
-        if (!ensurePaired()) {
-            return;
-        }
-
-        synchronized (mProfileLock) {
-            for (LocalBluetoothProfile profile : mProfiles) {
-                if (profile.isAutoConnectable()) {
-                    profile.setPreferred(mDevice, true);
-                    connectInt(profile);
-                }
-            }
+            mLocalAdapter.connectAllEnabledProfiles(mDevice);
         }
     }
 
@@ -703,7 +689,7 @@
          */
         if (!mProfiles.isEmpty()
                 && ((mConnectAttempted + timeout) > SystemClock.elapsedRealtime())) {
-            connectWithoutResettingTimer(false);
+            connectAllEnabledProfiles();
         }
 
         dispatchAttributesChanged();
@@ -722,7 +708,7 @@
         refresh();
 
         if (bondState == BluetoothDevice.BOND_BONDED && mDevice.isBondingInitiatedLocally()) {
-            connect(false);
+            connect();
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java
index 61e47f8..6e7a429 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java
@@ -87,8 +87,10 @@
         if (mMetricsFeature == null || mMetricsCategory == METRICS_CATEGORY_UNKNOWN) {
             return;
         }
-        final int elapse = (int) (SystemClock.elapsedRealtime() - mCreationTimestamp);
-        mMetricsFeature.action(METRICS_CATEGORY_UNKNOWN, action, mMetricsCategory, key, elapse);
+        if (mCreationTimestamp != 0L) {
+            final int elapse = (int) (SystemClock.elapsedRealtime() - mCreationTimestamp);
+            mMetricsFeature.action(METRICS_CATEGORY_UNKNOWN, action, mMetricsCategory, key, elapse);
+        }
     }
 
     /**
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
index 4ab9a9a..b07fc2b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
@@ -61,6 +61,8 @@
             "com.android.settings.category.ia.my_device_info";
     public static final String CATEGORY_BATTERY_SAVER_SETTINGS =
             "com.android.settings.category.ia.battery_saver_settings";
+    public static final String CATEGORY_SMART_BATTERY_SETTINGS =
+            "com.android.settings.category.ia.smart_battery_settings";
 
     public static final Map<String, String> KEY_COMPAT_MAP;
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 70b56ed..e85a102 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -122,7 +122,7 @@
             final CachedBluetoothDevice cachedDevice =
                     ((BluetoothMediaDevice) device).getCachedDevice();
             if (!cachedDevice.isConnected() && !cachedDevice.isBusy()) {
-                cachedDevice.connect(true);
+                cachedDevice.connect();
                 return;
             }
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
index 853c77e..05a6ce4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
@@ -40,7 +40,7 @@
         final NetworkTemplate mobileAll = NetworkTemplate.buildTemplateMobileAll(
                 telephonyManager.createForSubscriptionId(subId).getSubscriberId());
 
-        if (!subscriptionManager.isActiveSubId(subId)) {
+        if (!subscriptionManager.isActiveSubscriptionId(subId)) {
             Log.i(TAG, "Subscription is not active: " + subId);
             return mobileAll;
         }
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/OWNER b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/OWNER
new file mode 100644
index 0000000..5c2a7b8
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/OWNER
@@ -0,0 +1,4 @@
+# People who can approve changes for submission
+arcwang@google.com
+govenliu@google.com
+qal@google.com
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java
index 605c861..340a6c7 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java
@@ -59,8 +59,9 @@
         allKeys.add(CategoryKey.CATEGORY_SYSTEM_DEVELOPMENT);
         allKeys.add(CategoryKey.CATEGORY_GESTURES);
         allKeys.add(CategoryKey.CATEGORY_NIGHT_DISPLAY);
+        allKeys.add(CategoryKey.CATEGORY_SMART_BATTERY_SETTINGS);
         // DO NOT REMOVE ANYTHING ABOVE
 
-        assertThat(allKeys.size()).isEqualTo(18);
+        assertThat(allKeys.size()).isEqualTo(19);
     }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index 894aa78..c780a64 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -127,7 +127,7 @@
         mLocalMediaManager.registerCallback(mCallback);
         mLocalMediaManager.connectDevice(device);
 
-        verify(cachedDevice).connect(true);
+        verify(cachedDevice).connect();
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java
index d98f50b..b0a647e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java
@@ -31,6 +31,7 @@
 import android.telephony.TelephonyManager;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -71,12 +72,13 @@
         when(mTelephonyManager.getSubscriberId(SUB_ID)).thenReturn(SUBSCRIBER_ID);
         when(mTelephonyManager.getSubscriberId(SUB_ID_2)).thenReturn(SUBSCRIBER_ID_2);
         when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mTelephonyManager);
-        when(mSubscriptionManager.isActiveSubId(anyInt())).thenReturn(true);
+        when(mSubscriptionManager.isActiveSubscriptionId(anyInt())).thenReturn(true);
     }
 
     @Test
+    @Ignore
     public void getMobileTemplate_infoNull_returnMobileAll() {
-        when(mSubscriptionManager.isActiveSubId(SUB_ID)).thenReturn(false);
+        when(mSubscriptionManager.isActiveSubscriptionId(SUB_ID)).thenReturn(false);
 
         final NetworkTemplate networkTemplate = DataUsageUtils.getMobileTemplate(mContext, SUB_ID);
         assertThat(networkTemplate.matchesSubscriberId(SUBSCRIBER_ID)).isTrue();
@@ -84,6 +86,7 @@
     }
 
     @Test
+    @Ignore
     public void getMobileTemplate_groupUuidNull_returnMobileAll() {
         when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(mInfo1);
         when(mInfo1.getGroupUuid()).thenReturn(null);
@@ -96,6 +99,7 @@
     }
 
     @Test
+    @Ignore
     public void getMobileTemplate_groupUuidExist_returnMobileMerged() {
         when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(mInfo1);
         when(mInfo1.getGroupUuid()).thenReturn(mParcelUuid);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java
index 21aa526..2bd20a9 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java
@@ -72,7 +72,7 @@
 
         assertThat(AccessPointPreference.buildContentDescription(
                 RuntimeEnvironment.application, pref, ap))
-                .isEqualTo("ssid,connected,Wifi signal full.,Secure network");
+                .isEqualTo("ssid,connected,Wifi disconnected.,Secure network");
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/OWNER b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/OWNER
new file mode 100644
index 0000000..5c2a7b8
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/OWNER
@@ -0,0 +1,4 @@
+# People who can approve changes for submission
+arcwang@google.com
+govenliu@google.com
+qal@google.com
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 1e0c1d8..4443524 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -20,6 +20,7 @@
 import static android.os.Process.ROOT_UID;
 import static android.os.Process.SHELL_UID;
 import static android.os.Process.SYSTEM_UID;
+import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON_OVERLAY;
 
 import android.Manifest;
@@ -3304,7 +3305,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 185;
+            private static final int SETTINGS_VERSION = 186;
 
             private final int mUserId;
 
@@ -4547,6 +4548,32 @@
                     currentVersion = 185;
                 }
 
+                if (currentVersion == 185) {
+                    // Deprecate ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, and migrate it
+                    // to ACCESSIBILITY_BUTTON_TARGET_COMPONENT.
+                    final SettingsState secureSettings = getSecureSettingsLocked(userId);
+                    final Setting magnifyNavbarEnabled = secureSettings.getSettingLocked(
+                            Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);
+                    if ("1".equals(magnifyNavbarEnabled.getValue())) {
+                        secureSettings.insertSettingLocked(
+                                Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
+                                ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER,
+                                null /* tag */, false /* makeDefault */,
+                                SettingsState.SYSTEM_PACKAGE_NAME);
+                    } else {
+                        // Clear a11y button targets list setting. A11yManagerService will end up
+                        // adding all legacy enabled services that want the button to the list, so
+                        // there's no need to keep tracking them.
+                        secureSettings.insertSettingLocked(
+                                Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
+                                null, null /* tag */, false /* makeDefault */,
+                                SettingsState.SYSTEM_PACKAGE_NAME);
+                    }
+                    secureSettings.deleteSettingLocked(
+                            Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);
+                    currentVersion = 186;
+                }
+
                 // vXXX: Add new settings above this point.
 
                 if (currentVersion != newVersion) {
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 7278225..1a1a480 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -734,7 +734,8 @@
                  Settings.Secure.DOZE_WAKE_DISPLAY_GESTURE,
                  Settings.Secure.FACE_UNLOCK_RE_ENROLL,
                  Settings.Secure.TAP_GESTURE,
-                 Settings.Secure.WINDOW_MAGNIFICATION);
+                 Settings.Secure.WINDOW_MAGNIFICATION,
+                 Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER);
 
     @Test
     public void systemSettingsBackedUpOrBlacklisted() {
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 2a1e74e..bdb594f 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -211,6 +211,7 @@
 
     <!-- accessibility -->
     <uses-permission android:name="android.permission.MODIFY_ACCESSIBILITY_DATA" />
+    <uses-permission android:name="android.permission.MANAGE_ACCESSIBILITY" />
 
     <!-- to control accessibility volume -->
     <uses-permission android:name="android.permission.CHANGE_ACCESSIBILITY_VOLUME" />
@@ -307,7 +308,8 @@
         </receiver>
 
         <activity android:name=".screenrecord.ScreenRecordDialog"
-            android:theme="@style/ScreenRecord" />
+            android:theme="@style/ScreenRecord"
+            android:excludeFromRecents="true" />
         <service android:name=".screenrecord.RecordingService" />
 
         <receiver android:name=".SysuiRestartReceiver"
@@ -636,6 +638,23 @@
             </intent-filter>
         </activity>
 
+        <activity android:name=".controls.management.ControlsProviderSelectorActivity"
+                  android:label="Controls Providers"
+                  android:theme="@style/Theme.SystemUI"
+                  android:exported="true"
+                  android:excludeFromRecents="true"
+                  android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
+                  android:visibleToInstantApps="true">
+        </activity>
+
+        <activity android:name=".controls.management.ControlsFavoritingActivity"
+                  android:parentActivityName=".controls.management.ControlsProviderSelectorActivity"
+                  android:theme="@style/Theme.SystemUI"
+                  android:excludeFromRecents="true"
+                  android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
+                  android:visibleToInstantApps="true">
+        </activity>
+
         <!-- Doze with notifications, run in main sysui process for every user  -->
         <service
             android:name=".doze.DozeService"
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java
index 8db0d02..02c4c5e 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java
@@ -46,6 +46,8 @@
      */
     public void snooze(StatusBarNotification sbn, SnoozeOption snoozeOption);
 
+    public void snooze(StatusBarNotification sbn, int hours);
+
     public float getMinDismissVelocity();
 
     public boolean isDismissGesture(MotionEvent ev);
diff --git a/packages/SystemUI/res/drawable/ic_add_to_home.xml b/packages/SystemUI/res/drawable/ic_add_to_home.xml
new file mode 100644
index 0000000..2b40c62
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_add_to_home.xml
@@ -0,0 +1,26 @@
+<!--
+  Copyright (C) 2020 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0"
+        android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M18,1.01L8,1c-1.1,0 -2,0.9 -2,2v3h2V5h10v14H8v-1H6v3c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-1.99 -2,-1.99zM10,15h2V8H5v2h3.59L3,15.59 4.41,17 10,11.41V15z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_demote_conversation.xml b/packages/SystemUI/res/drawable/ic_demote_conversation.xml
new file mode 100644
index 0000000..5a88160
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_demote_conversation.xml
@@ -0,0 +1,25 @@
+<!--
+  Copyright (C) 2020 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M20,2L4.83,2l2,2L20,4v12h-1.17l1.87,1.87c0.75,-0.29 1.3,-1.02 1.3,-1.87L22,4c0,-1.1 -0.9,-2 -2,-2zM6,12h2v2L6,14zM18,11L18,9h-6.17l2,2zM18,6h-8v1.17l0.83,0.83L18,8zM0.69,3.51l1.32,1.32L2,22l4,-4h9.17l5.31,5.31 1.41,-1.41L2.1,2.1 0.69,3.51zM6,16h-0.83l-0.59,0.59 -0.58,0.58L4,6.83l2,2L6,11h2.17l5,5L6,16z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_qs_screenrecord.xml b/packages/SystemUI/res/drawable/ic_qs_screenrecord.xml
new file mode 100644
index 0000000..687c9c4
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_screenrecord.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M18,10.48L18,6c0,-1.1 -0.9,-2 -2,-2L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2v-4.48l4,3.98v-11l-4,3.98zM16,9.69L16,18L4,18L4,6h12v3.69z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_snooze.xml b/packages/SystemUI/res/drawable/ic_snooze.xml
index b0b03a9..f4c074d 100644
--- a/packages/SystemUI/res/drawable/ic_snooze.xml
+++ b/packages/SystemUI/res/drawable/ic_snooze.xml
@@ -1,12 +1,24 @@
+<!--
+  Copyright (C) 2017 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License
+  -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
         android:width="24dp"
         android:height="24dp"
         android:viewportWidth="24.0"
         android:viewportHeight="24.0">
     <path
-        android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"
-        android:fillColor="#757575"/>
-    <path
-        android:pathData="M12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z"
-        android:fillColor="#757575"/>
+        android:fillColor="@android:color/white"
+        android:pathData="M9,11h3.63L9,15.2L9,17h6v-2h-3.63L15,10.8L15,9L9,9v2zM16.056,3.346l1.282,-1.535 4.607,3.85 -1.28,1.54zM3.336,7.19l-1.28,-1.536L6.662,1.81l1.28,1.536zM12,6c3.86,0 7,3.14 7,7s-3.14,7 -7,7 -7,-3.14 -7,-7 3.14,-7 7,-7m0,-2c-4.97,0 -9,4.03 -9,9s4.03,9 9,9 9,-4.03 9,-9 -4.03,-9 -9,-9z"/>
 </vector>
diff --git a/packages/SystemUI/res/drawable/ic_star.xml b/packages/SystemUI/res/drawable/ic_star.xml
new file mode 100644
index 0000000..4a731b3
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_star.xml
@@ -0,0 +1,25 @@
+<!--
+  Copyright (C) 2020 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_star_border.xml b/packages/SystemUI/res/drawable/ic_star_border.xml
new file mode 100644
index 0000000..9ede40b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_star_border.xml
@@ -0,0 +1,25 @@
+<!--
+  Copyright (C) 2020 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/app_item.xml b/packages/SystemUI/res/layout/app_item.xml
new file mode 100644
index 0000000..83e7887
--- /dev/null
+++ b/packages/SystemUI/res/layout/app_item.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2019 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="?android:attr/selectableItemBackground"
+    android:gravity="center_vertical"
+    android:minHeight="?android:attr/listPreferredItemHeightSmall"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+    <LinearLayout
+        android:id="@+id/icon_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="start|center_vertical"
+        android:minWidth="56dp"
+        android:orientation="horizontal"
+        android:paddingEnd="8dp"
+        android:paddingTop="4dp"
+        android:paddingBottom="4dp">
+        <ImageView
+            android:id="@android:id/icon"
+            android:layout_width="@dimen/app_icon_size"
+            android:layout_height="@dimen/app_icon_size"/>
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:orientation="vertical"
+        android:paddingTop="16dp"
+        android:paddingBottom="16dp">
+
+        <TextView
+            android:id="@android:id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:ellipsize="marquee"
+            android:fadingEdge="horizontal"
+            android:singleLine="true"
+            android:textAppearance="?android:attr/textAppearanceListItem"/>
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@android:id/widget_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:gravity="center_vertical|end"
+        android:minWidth="64dp"
+        android:orientation="vertical"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/control_item.xml b/packages/SystemUI/res/layout/control_item.xml
new file mode 100644
index 0000000..85701aa
--- /dev/null
+++ b/packages/SystemUI/res/layout/control_item.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2020 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="100dp"
+    android:padding="15dp"
+    android:clickable="true"
+    android:focusable="true">
+
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <TextView
+        android:id="@+id/status"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="12sp"
+        android:textColor="?android:attr/textColorPrimary"
+        android:fontFamily="@*android:string/config_bodyFontFamily"
+        android:paddingLeft="3dp"
+        app:layout_constraintBottom_toBottomOf="@+id/icon"
+        app:layout_constraintStart_toEndOf="@+id/icon" />
+
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="18sp"
+        android:textColor="?android:attr/textColorPrimary"
+        android:fontFamily="@*android:string/config_headlineFontFamily"
+        app:layout_constraintBottom_toTopOf="@+id/subtitle"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/icon" />
+
+    <TextView
+        android:id="@+id/subtitle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="16sp"
+        android:textColor="?android:attr/textColorSecondary"
+        android:fontFamily="@*android:string/config_headlineFontFamily"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toStartOf="parent" />
+
+    <CheckBox
+        android:id="@+id/favorite"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"/>
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/layout/notification_conversation_info.xml b/packages/SystemUI/res/layout/notification_conversation_info.xml
new file mode 100644
index 0000000..8749b1a
--- /dev/null
+++ b/packages/SystemUI/res/layout/notification_conversation_info.xml
@@ -0,0 +1,254 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2020, The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<com.android.systemui.statusbar.notification.row.NotificationConversationInfo
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/notification_guts"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:clickable="true"
+    android:clipChildren="false"
+    android:clipToPadding="true"
+    android:orientation="vertical"
+    android:background="@color/notification_material_background_color"
+    android:paddingStart="@*android:dimen/notification_content_margin_start">
+
+    <!-- Package Info -->
+    <RelativeLayout
+        android:id="@+id/header"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/notification_guts_conversation_header_height"
+        android:gravity="center_vertical"
+        android:clipChildren="false"
+        android:clipToPadding="false">
+        <ImageView
+            android:id="@+id/conversation_icon"
+            android:layout_width="@dimen/notification_guts_conversation_icon_size"
+            android:layout_height="@dimen/notification_guts_conversation_icon_size"
+            android:layout_centerVertical="true"
+            android:layout_alignParentStart="true"
+            android:layout_marginEnd="6dp" />
+        <LinearLayout
+            android:id="@+id/names"
+            android:orientation="vertical"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:minHeight="@dimen/notification_guts_conversation_icon_size"
+            android:layout_centerVertical="true"
+            android:gravity="center_vertical"
+            android:layout_toEndOf="@id/conversation_icon">
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:gravity="start"
+                android:orientation="horizontal">
+                <TextView
+                    android:id="@+id/parent_channel_name"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="1"
+                    style="@style/TextAppearance.NotificationImportanceChannel"/>
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_centerVertical="true"
+                    style="@style/TextAppearance.NotificationImportanceHeader"
+                    android:layout_marginStart="2dp"
+                    android:layout_marginEnd="2dp"
+                    android:text="@*android:string/notification_header_divider_symbol" />
+                <TextView
+                    android:id="@+id/name"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="1"
+                    style="@style/TextAppearance.NotificationImportanceChannel"/>
+            </LinearLayout>
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:gravity="start"
+                android:orientation="horizontal">
+                <TextView
+                    android:id="@+id/pkg_name"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    style="@style/TextAppearance.NotificationImportanceChannelGroup"
+                    android:ellipsize="end"
+                    android:maxLines="1"/>
+                <TextView
+                    android:id="@+id/group_divider"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_centerVertical="true"
+                    style="@style/TextAppearance.NotificationImportanceHeader"
+                    android:layout_marginStart="2dp"
+                    android:layout_marginEnd="2dp"
+                    android:text="@*android:string/notification_header_divider_symbol" />
+                <TextView
+                    android:id="@+id/group_name"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="1"
+                    style="@style/TextAppearance.NotificationImportanceChannel"/>
+            </LinearLayout>
+
+        </LinearLayout>
+
+        <TextView
+            android:id="@+id/pkg_divider"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerVertical="true"
+            style="@style/TextAppearance.NotificationImportanceHeader"
+            android:layout_marginStart="2dp"
+            android:layout_marginEnd="2dp"
+            android:layout_toEndOf="@id/name"
+            android:text="@*android:string/notification_header_divider_symbol" />
+        <TextView
+            android:id="@+id/delegate_name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerVertical="true"
+            style="@style/TextAppearance.NotificationImportanceHeader"
+            android:layout_marginStart="2dp"
+            android:layout_marginEnd="2dp"
+            android:ellipsize="end"
+            android:text="@string/notification_delegate_header"
+            android:layout_toEndOf="@id/pkg_divider"
+            android:maxLines="1" />
+
+        <!-- end aligned fields -->
+        <ImageButton
+            android:id="@+id/demote"
+            android:layout_width="@dimen/notification_importance_toggle_size"
+            android:layout_height="@dimen/notification_importance_toggle_size"
+            android:layout_centerVertical="true"
+            android:background="@drawable/ripple_drawable"
+            android:contentDescription="@string/demote"
+            android:src="@drawable/ic_demote_conversation"
+            android:layout_toStartOf="@id/app_settings"
+            android:tint="@color/notification_guts_link_icon_tint"/>
+        <!-- Optional link to app. Only appears if the channel is not disabled and the app
+asked for it -->
+        <ImageButton
+            android:id="@+id/app_settings"
+            android:layout_width="@dimen/notification_importance_toggle_size"
+            android:layout_height="@dimen/notification_importance_toggle_size"
+            android:layout_centerVertical="true"
+            android:visibility="gone"
+            android:background="@drawable/ripple_drawable"
+            android:contentDescription="@string/notification_app_settings"
+            android:src="@drawable/ic_info"
+            android:layout_toStartOf="@id/info"
+            android:tint="@color/notification_guts_link_icon_tint"/>
+        <ImageButton
+            android:id="@+id/info"
+            android:layout_width="@dimen/notification_importance_toggle_size"
+            android:layout_height="@dimen/notification_importance_toggle_size"
+            android:layout_centerVertical="true"
+            android:background="@drawable/ripple_drawable"
+            android:contentDescription="@string/notification_more_settings"
+            android:src="@drawable/ic_settings"
+            android:layout_alignParentEnd="true"
+            android:tint="@color/notification_guts_link_icon_tint"/>
+    </RelativeLayout>
+
+    <LinearLayout
+        android:id="@+id/actions"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@*android:dimen/notification_content_margin_end"
+        android:clipChildren="false"
+        android:clipToPadding="false"
+        android:orientation="vertical">
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="0.5dp"
+            android:background="@color/GM2_grey_300" />
+        <Button
+            android:id="@+id/bubble"
+            android:layout_height="@dimen/notification_guts_conversation_action_height"
+            android:layout_width="match_parent"
+            style="?android:attr/borderlessButtonStyle"
+            android:text="@string/notification_conversation_favorite"
+            android:gravity="left|center_vertical"
+            android:drawableStart="@drawable/ic_create_bubble"
+            android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start"
+            android:drawableTint="@color/notification_guts_link_icon_tint"/>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="0.5dp"
+            android:background="@color/GM2_grey_300" />
+        <Button
+            android:id="@+id/home"
+            android:layout_height="@dimen/notification_guts_conversation_action_height"
+            android:layout_width="match_parent"
+            style="?android:attr/borderlessButtonStyle"
+            android:text="@string/notification_conversation_home_screen"
+            android:gravity="left|center_vertical"
+            android:drawableStart="@drawable/ic_add_to_home"
+            android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start"
+            android:drawableTint="@color/notification_guts_link_icon_tint"/>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="0.5dp"
+            android:background="@color/GM2_grey_300" />
+        <Button
+            android:id="@+id/fave"
+            android:layout_height="@dimen/notification_guts_conversation_action_height"
+            android:layout_width="match_parent"
+            style="?android:attr/borderlessButtonStyle"
+            android:gravity="left|center_vertical"
+            android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start"
+            android:drawableTint="@color/notification_guts_link_icon_tint"/>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="0.5dp"
+            android:background="@color/GM2_grey_300" />
+        <Button
+            android:id="@+id/snooze"
+            android:layout_height="@dimen/notification_guts_conversation_action_height"
+            android:layout_width="match_parent"
+            style="?android:attr/borderlessButtonStyle"
+            android:text="@string/notification_menu_snooze_action"
+            android:gravity="left|center_vertical"
+            android:drawableStart="@drawable/ic_snooze"
+            android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start"
+            android:drawableTint="@color/notification_guts_link_icon_tint"/>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="0.5dp"
+            android:background="@color/GM2_grey_300" />
+        <Button
+            android:id="@+id/mute"
+            android:layout_height="@dimen/notification_guts_conversation_action_height"
+            android:layout_width="match_parent"
+            style="?android:attr/borderlessButtonStyle"
+            android:text="@string/notification_conversation_mute"
+            android:gravity="left|center_vertical"
+            android:drawableStart="@drawable/ic_notifications_silence"
+            android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start"
+            android:drawableTint="@color/notification_guts_link_icon_tint"/>
+
+        </LinearLayout>
+
+</com.android.systemui.statusbar.notification.row.NotificationConversationInfo>
diff --git a/packages/SystemUI/res/layout/screen_record_dialog.xml b/packages/SystemUI/res/layout/screen_record_dialog.xml
deleted file mode 100644
index 3d63b7d..0000000
--- a/packages/SystemUI/res/layout/screen_record_dialog.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              android:layout_width="match_parent"
-              android:layout_height="match_parent"
-              android:clipChildren="false"
-              android:clipToPadding="false"
-              android:gravity="top"
-              android:orientation="vertical"
-              android:padding="@dimen/global_actions_padding"
-              android:background="@drawable/rounded_bg_full">
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="vertical">
-        <CheckBox
-            android:id="@+id/checkbox_mic"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:textAppearance="?android:attr/textAppearanceMedium"
-            android:text="@string/screenrecord_mic_label"/>
-        <CheckBox
-            android:id="@+id/checkbox_taps"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:textAppearance="?android:attr/textAppearanceMedium"
-            android:text="@string/screenrecord_taps_label"/>
-        <Button
-            android:id="@+id/record_button"
-            android:layout_width="match_parent"
-            android:layout_height="50dp"
-            android:text="@string/screenrecord_start_label"
-        />
-    </LinearLayout>
-
-</LinearLayout>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 640f31b..8963157 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -109,7 +109,7 @@
 
     <!-- The default tiles to display in QuickSettings -->
     <string name="quick_settings_tiles_default" translatable="false">
-        wifi,bt,dnd,flashlight,rotation,battery,cell,airplane,cast
+        wifi,bt,dnd,flashlight,rotation,battery,cell,airplane,cast,screenrecord
     </string>
 
     <!-- The minimum number of tiles to display in QuickSettings -->
@@ -117,7 +117,7 @@
 
     <!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
     <string name="quick_settings_tiles_stock" translatable="false">
-        wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,controls
+        wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,controls,screenrecord
     </string>
 
     <!-- The tiles to display in QuickSettings -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index c26cb9a..53df025 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -213,6 +213,11 @@
     <!-- The horizontal space around the buttons in the inline settings -->
     <dimen name="notification_guts_button_horizontal_spacing">8dp</dimen>
 
+    <dimen name="notification_guts_conversation_header_height">84dp</dimen>
+    <dimen name="notification_guts_conversation_icon_size">52dp</dimen>
+    <dimen name="notification_guts_conversation_action_height">56dp</dimen>
+    <dimen name="notification_guts_conversation_action_text_padding_start">32dp</dimen>
+
     <!-- The height of the header in inline settings -->
     <dimen name="notification_guts_header_height">24dp</dimen>
 
@@ -1160,4 +1165,5 @@
     <dimen name="magnifier_up_down_controls_width">45dp</dimen>
     <dimen name="magnifier_up_down_controls_height">40dp</dimen>
 
+    <dimen name="app_icon_size">32dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 4f532b7..9129938 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -928,6 +928,13 @@
     <!-- QuickSettings: NFC (on) [CHAR LIMIT=NONE] -->
     <string name="quick_settings_nfc_on">NFC is enabled</string>
 
+    <!-- QuickSettings: Screen record tile [CHAR LIMIT=NONE] -->
+    <string name="quick_settings_screen_record_label">Screen Record</string>
+    <!-- QuickSettings: Text to prompt the user to begin a new recording [CHAR LIMIT=20] -->
+    <string name="quick_settings_screen_record_start">Start</string>
+    <!-- QuickSettings: Text to prompt the user to stop an ongoing recording [CHAR LIMIT=20] -->
+    <string name="quick_settings_screen_record_stop">Stop</string>
+
     <!-- Recents: Text that shows above the navigation bar after launching a few apps. [CHAR LIMIT=NONE] -->
     <string name="recents_swipe_up_onboarding">Swipe up to switch apps</string>
     <!-- Recents: Text that shows above the navigation bar after launching several apps. [CHAR LIMIT=NONE] -->
@@ -1791,6 +1798,29 @@
     <string name="notification_done">Done</string>
     <!-- Notification: inline controls: undo block button -->
     <string name="inline_undo">Undo</string>
+    <!-- Notification: Conversation: control panel, label for button that demotes notification from conversation to normal notification -->
+    <string name="demote">Mark this notification as not a conversation</string>
+
+    <!-- [CHAR LIMIT=100] Mark this conversation as a favorite -->
+    <string name="notification_conversation_favorite">Favorite</string>
+
+    <!-- [CHAR LIMIT=100] Unmark this conversation as a favorite -->
+    <string name="notification_conversation_unfavorite">Unfavorite</string>
+
+    <!-- [CHAR LIMIT=100] Mute this conversation -->
+    <string name="notification_conversation_mute">Mute</string>
+
+    <!-- [CHAR LIMIT=100] Umute this conversation -->
+    <string name="notification_conversation_unmute">Unmute</string>
+
+    <!-- [CHAR LIMIT=100] Show notification as bubble -->
+    <string name="notification_conversation_bubble">Show as bubble</string>
+
+    <!-- [CHAR LIMIT=100] Turn off bubbles for notification -->
+    <string name="notification_conversation_unbubble">Turn off bubbles</string>
+
+    <!-- [CHAR LIMIT=100] Add this conversation to home screen -->
+    <string name="notification_conversation_home_screen">Add to home screen</string>
 
     <!-- Notification: Menu row: Content description for menu items. [CHAR LIMIT=NONE] -->
     <string name="notification_menu_accessibility"><xliff:g id="app_name" example="YouTube">%1$s</xliff:g> <xliff:g id="menu_description" example="notification controls">%2$s</xliff:g></string>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index df0dc46..e475ef1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -387,8 +387,8 @@
      * Refresh the time of the clock, due to either time tick broadcast or doze time tick alarm.
      */
     public void refresh() {
-        mClockView.refresh();
-        mClockViewBold.refresh();
+        mClockView.refreshTime();
+        mClockViewBold.refreshTime();
         if (mClockPlugin != null) {
             mClockPlugin.onTimeTick();
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java
index eba2400..99e122e 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java
@@ -188,7 +188,7 @@
     public void onTimeTick() {
         mAnalogClock.onTimeChanged();
         mBigClockView.onTimeChanged();
-        mLockClock.refresh();
+        mLockClock.refreshTime();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java
index 3a2fbe5..fac923c01 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java
@@ -195,7 +195,7 @@
     public void onTimeTick() {
         mAnalogClock.onTimeChanged();
         mView.onTimeChanged();
-        mLockClock.refresh();
+        mLockClock.refreshTime();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index bbe972d..d149591 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -56,6 +56,7 @@
 import com.android.systemui.power.PowerUI;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.recents.Recents;
+import com.android.systemui.screenrecord.RecordingController;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
@@ -323,6 +324,7 @@
     @Inject Lazy<DisplayWindowController> mDisplayWindowController;
     @Inject Lazy<SystemWindows> mSystemWindows;
     @Inject Lazy<DisplayImeController> mDisplayImeController;
+    @Inject Lazy<RecordingController> mRecordingController;
 
     @Inject
     public Dependency() {
@@ -519,6 +521,8 @@
         //                    Dependency problem.
         mProviders.put(AutoHideController.class, mAutoHideController::get);
 
+        mProviders.put(RecordingController.class, mRecordingController::get);
+
         sDependency = this;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt
new file mode 100644
index 0000000..e6cdf50
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls
+
+import android.service.controls.Control
+
+data class ControlStatus(val control: Control, val favorite: Boolean, val removed: Boolean = false)
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
new file mode 100644
index 0000000..265ddd8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls
+
+import android.content.Context
+import android.content.pm.ServiceInfo
+import com.android.settingslib.applications.DefaultAppInfo
+
+class ControlsServiceInfo(
+    context: Context,
+    serviceInfo: ServiceInfo
+) : DefaultAppInfo(
+    context,
+    context.packageManager,
+    context.userId,
+    serviceInfo.componentName
+)
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlInfo.kt
new file mode 100644
index 0000000..b6cca3f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlInfo.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.controller
+
+import android.content.ComponentName
+import android.service.controls.DeviceTypes
+import android.util.Log
+
+/**
+ * Stores basic information about a [Control] to persist and keep track of favorites.
+ */
+data class ControlInfo(
+    val component: ComponentName,
+    val controlId: String,
+    val controlTitle: CharSequence,
+    @DeviceTypes.DeviceType val deviceType: Int
+) {
+
+    companion object {
+        private const val TAG = "ControlInfo"
+        private const val SEPARATOR = ":"
+        fun createFromString(string: String): ControlInfo? {
+            val parts = string.split(SEPARATOR)
+            val component = ComponentName.unflattenFromString(parts[0])
+            if (parts.size != 4 || component == null) {
+                Log.e(TAG, "Cannot parse ControlInfo from $string")
+                return null
+            }
+            val type = try {
+                parts[3].toInt()
+            } catch (e: Exception) {
+                Log.e(TAG, "Cannot parse deviceType from ${parts[3]}")
+                return null
+            }
+            return ControlInfo(
+                    component,
+                    parts[1],
+                    parts[2],
+                    if (DeviceTypes.validDeviceType(type)) type else DeviceTypes.TYPE_UNKNOWN)
+        }
+    }
+    override fun toString(): String {
+        return component.flattenToString() +
+                "$SEPARATOR$controlId$SEPARATOR$controlTitle$SEPARATOR$deviceType"
+    }
+
+    class Builder {
+        lateinit var componentName: ComponentName
+        lateinit var controlId: String
+        lateinit var controlTitle: CharSequence
+        var deviceType: Int = DeviceTypes.TYPE_UNKNOWN
+
+        fun build() = ControlInfo(componentName, controlId, controlTitle, deviceType)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt
new file mode 100644
index 0000000..6b7fc4b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.controller
+
+import android.content.ComponentName
+import android.service.controls.Control
+import android.service.controls.actions.ControlAction
+
+interface ControlsBindingController {
+    fun bindAndLoad(component: ComponentName, callback: (List<Control>) -> Unit)
+    fun bindServices(components: List<ComponentName>)
+    fun subscribe(controls: List<ControlInfo>)
+    fun action(controlInfo: ControlInfo, action: ControlAction)
+    fun unsubscribe()
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
new file mode 100644
index 0000000..80e48b9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.controller
+
+import android.content.ComponentName
+import android.content.Context
+import android.os.IBinder
+import android.service.controls.Control
+import android.service.controls.IControlsProviderCallback
+import android.service.controls.actions.ControlAction
+import android.util.ArrayMap
+import android.util.Log
+import com.android.internal.annotations.GuardedBy
+import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.concurrency.DelayableExecutor
+import dagger.Lazy
+import java.util.concurrent.atomic.AtomicBoolean
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+@VisibleForTesting
+open class ControlsBindingControllerImpl @Inject constructor(
+    private val context: Context,
+    @Background private val backgroundExecutor: DelayableExecutor,
+    private val lazyController: Lazy<ControlsController>
+) : ControlsBindingController {
+
+    companion object {
+        private const val TAG = "ControlsBindingControllerImpl"
+    }
+
+    private val refreshing = AtomicBoolean(false)
+
+    @GuardedBy("componentMap")
+    private val tokenMap: MutableMap<IBinder, ControlsProviderLifecycleManager> =
+            ArrayMap<IBinder, ControlsProviderLifecycleManager>()
+    @GuardedBy("componentMap")
+    private val componentMap: MutableMap<ComponentName, ControlsProviderLifecycleManager> =
+            ArrayMap<ComponentName, ControlsProviderLifecycleManager>()
+
+    private val serviceCallback = object : IControlsProviderCallback.Stub() {
+        override fun onLoad(token: IBinder, controls: MutableList<Control>) {
+            backgroundExecutor.execute(OnLoadRunnable(token, controls))
+        }
+
+        override fun onRefreshState(token: IBinder, controlStates: List<Control>) {
+            if (!refreshing.get()) {
+                Log.d(TAG, "Refresh outside of window for token:$token")
+            } else {
+                backgroundExecutor.execute(OnRefreshStateRunnable(token, controlStates))
+            }
+        }
+
+        override fun onControlActionResponse(
+            token: IBinder,
+            controlId: String,
+            @ControlAction.ResponseResult response: Int
+        ) {
+            backgroundExecutor.execute(OnActionResponseRunnable(token, controlId, response))
+        }
+    }
+
+    @VisibleForTesting
+    internal open fun createProviderManager(component: ComponentName):
+            ControlsProviderLifecycleManager {
+        return ControlsProviderLifecycleManager(
+                context,
+                backgroundExecutor,
+                serviceCallback,
+                component
+        )
+    }
+
+    private fun retrieveLifecycleManager(component: ComponentName):
+            ControlsProviderLifecycleManager {
+        synchronized(componentMap) {
+            val provider = componentMap.getOrPut(component) {
+                createProviderManager(component)
+            }
+            tokenMap.putIfAbsent(provider.token, provider)
+            return provider
+        }
+    }
+
+    override fun bindAndLoad(component: ComponentName, callback: (List<Control>) -> Unit) {
+        val provider = retrieveLifecycleManager(component)
+        provider.maybeBindAndLoad(callback)
+    }
+
+    override fun subscribe(controls: List<ControlInfo>) {
+        val controlsByComponentName = controls.groupBy { it.component }
+        if (refreshing.compareAndSet(false, true)) {
+            controlsByComponentName.forEach {
+                val provider = retrieveLifecycleManager(it.key)
+                backgroundExecutor.execute {
+                    provider.maybeBindAndSubscribe(it.value.map { it.controlId })
+                }
+            }
+        }
+        // Unbind unneeded providers
+        val providersWithFavorites = controlsByComponentName.keys
+        synchronized(componentMap) {
+            componentMap.forEach {
+                if (it.key !in providersWithFavorites) {
+                    backgroundExecutor.execute { it.value.unbindService() }
+                }
+            }
+        }
+    }
+
+    override fun unsubscribe() {
+        if (refreshing.compareAndSet(true, false)) {
+            val providers = synchronized(componentMap) {
+                componentMap.values.toList()
+            }
+            providers.forEach {
+                backgroundExecutor.execute { it.unsubscribe() }
+            }
+        }
+    }
+
+    override fun action(controlInfo: ControlInfo, action: ControlAction) {
+        val provider = retrieveLifecycleManager(controlInfo.component)
+        provider.maybeBindAndSendAction(controlInfo.controlId, action)
+    }
+
+    override fun bindServices(components: List<ComponentName>) {
+        components.forEach {
+            val provider = retrieveLifecycleManager(it)
+            backgroundExecutor.execute { provider.bindPermanently() }
+        }
+    }
+
+    private abstract inner class CallbackRunnable(val token: IBinder) : Runnable {
+        protected val provider: ControlsProviderLifecycleManager? =
+                synchronized(componentMap) {
+                    tokenMap.get(token)
+                }
+    }
+
+    private inner class OnLoadRunnable(
+        token: IBinder,
+        val list: List<Control>
+    ) : CallbackRunnable(token) {
+        override fun run() {
+            if (provider == null) {
+                Log.e(TAG, "No provider found for token:$token")
+                return
+            }
+            synchronized(componentMap) {
+                if (token !in tokenMap.keys) {
+                    Log.e(TAG, "Provider for token:$token does not exist anymore")
+                    return
+                }
+            }
+            provider.lastLoadCallback?.invoke(list) ?: run {
+                Log.w(TAG, "Null callback")
+            }
+            provider.maybeUnbindAndRemoveCallback()
+        }
+    }
+
+    private inner class OnRefreshStateRunnable(
+        token: IBinder,
+        val list: List<Control>
+    ) : CallbackRunnable(token) {
+        override fun run() {
+            if (!refreshing.get()) {
+                Log.d(TAG, "onRefresh outside of window from:${provider?.componentName}")
+            }
+            provider?.let {
+                lazyController.get().refreshStatus(it.componentName, list)
+            }
+        }
+    }
+
+    private inner class OnActionResponseRunnable(
+        token: IBinder,
+        val controlId: String,
+        @ControlAction.ResponseResult val response: Int
+    ) : CallbackRunnable(token) {
+        override fun run() {
+            provider?.let {
+                lazyController.get().onActionResponse(it.componentName, controlId, response)
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
new file mode 100644
index 0000000..4d95822
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.controller
+
+import android.content.ComponentName
+import android.service.controls.Control
+import android.service.controls.actions.ControlAction
+import com.android.systemui.controls.ControlStatus
+
+interface ControlsController {
+    val available: Boolean
+
+    fun getFavoriteControls(): List<ControlInfo>
+    fun loadForComponent(componentName: ComponentName, callback: (List<ControlStatus>) -> Unit)
+    fun subscribeToFavorites()
+    fun changeFavoriteStatus(controlInfo: ControlInfo, state: Boolean)
+    fun unsubscribe()
+    fun action(controlInfo: ControlInfo, action: ControlAction)
+    fun refreshStatus(componentName: ComponentName, controls: List<Control>)
+    fun onActionResponse(
+        componentName: ComponentName,
+        controlId: String,
+        @ControlAction.ResponseResult response: Int
+    )
+    fun clearFavorites()
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
new file mode 100644
index 0000000..7e328e4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.controller
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.os.Environment
+import android.provider.Settings
+import android.service.controls.Control
+import android.service.controls.actions.ControlAction
+import android.util.ArrayMap
+import android.util.Log
+import com.android.internal.annotations.GuardedBy
+import com.android.systemui.DumpController
+import com.android.systemui.Dumpable
+import com.android.systemui.controls.ControlStatus
+import com.android.systemui.controls.management.ControlsFavoritingActivity
+import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.concurrency.DelayableExecutor
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import java.util.Optional
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class ControlsControllerImpl @Inject constructor (
+    private val context: Context,
+    @Background private val executor: DelayableExecutor,
+    private val uiController: ControlsUiController,
+    private val bindingController: ControlsBindingController,
+    private val optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>,
+    dumpController: DumpController
+) : Dumpable, ControlsController {
+
+    companion object {
+        private const val TAG = "ControlsControllerImpl"
+        const val CONTROLS_AVAILABLE = "systemui.controls_available"
+    }
+
+    override val available = Settings.Secure.getInt(
+            context.contentResolver, CONTROLS_AVAILABLE, 0) != 0
+    val persistenceWrapper = optionalWrapper.orElseGet {
+        ControlsFavoritePersistenceWrapper(
+                Environment.buildPath(
+                        context.filesDir,
+                        ControlsFavoritePersistenceWrapper.FILE_NAME),
+                executor
+        )
+    }
+
+    // Map of map: ComponentName -> (String -> ControlInfo)
+    @GuardedBy("currentFavorites")
+    private val currentFavorites = ArrayMap<ComponentName, MutableMap<String, ControlInfo>>()
+
+    init {
+        if (available) {
+            dumpController.registerDumpable(this)
+            loadFavorites()
+        }
+    }
+
+    private fun loadFavorites() {
+        val infos = persistenceWrapper.readFavorites()
+        synchronized(currentFavorites) {
+            infos.forEach {
+                currentFavorites.getOrPut(it.component, { ArrayMap<String, ControlInfo>() })
+                        .put(it.controlId, it)
+            }
+        }
+    }
+
+    override fun loadForComponent(
+        componentName: ComponentName,
+        callback: (List<ControlStatus>) -> Unit
+    ) {
+        if (!available) {
+            Log.d(TAG, "Controls not available")
+            return
+        }
+        bindingController.bindAndLoad(componentName) {
+            synchronized(currentFavorites) {
+                val favoritesForComponentKeys: Set<String> =
+                        currentFavorites.get(componentName)?.keys ?: emptySet()
+                val changed = updateFavoritesLocked(componentName, it)
+                if (changed) {
+                    persistenceWrapper.storeFavorites(favoritesAsListLocked())
+                }
+                val removed = findRemovedLocked(favoritesForComponentKeys, it)
+                callback(removed.map { currentFavorites.getValue(componentName).getValue(it) }
+                            .map(::createRemovedStatus) +
+                        it.map { ControlStatus(it, it.controlId in favoritesForComponentKeys) })
+            }
+        }
+    }
+
+    private fun createRemovedStatus(controlInfo: ControlInfo): ControlStatus {
+        val intent = Intent(context, ControlsFavoritingActivity::class.java).apply {
+            putExtra(ControlsFavoritingActivity.EXTRA_COMPONENT, controlInfo.component)
+            flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP
+        }
+        val pendingIntent = PendingIntent.getActivity(context,
+                controlInfo.component.hashCode(),
+                intent,
+                0)
+        val control = Control.StatelessBuilder(controlInfo.controlId, pendingIntent)
+                .setTitle(controlInfo.controlTitle)
+                .setDeviceType(controlInfo.deviceType)
+                .build()
+        return ControlStatus(control, true, true)
+    }
+
+    @GuardedBy("currentFavorites")
+    private fun findRemovedLocked(favoriteKeys: Set<String>, list: List<Control>): Set<String> {
+        val controlsKeys = list.map { it.controlId }
+        return favoriteKeys.minus(controlsKeys)
+    }
+
+    @GuardedBy("currentFavorites")
+    private fun updateFavoritesLocked(componentName: ComponentName, list: List<Control>): Boolean {
+        val favorites = currentFavorites.get(componentName) ?: mutableMapOf()
+        val favoriteKeys = favorites.keys
+        if (favoriteKeys.isEmpty()) return false // early return
+        var changed = false
+        list.forEach {
+            if (it.controlId in favoriteKeys) {
+                val value = favorites.getValue(it.controlId)
+                if (value.controlTitle != it.title || value.deviceType != it.deviceType) {
+                    favorites[it.controlId] = value.copy(controlTitle = it.title,
+                            deviceType = it.deviceType)
+                    changed = true
+                }
+            }
+        }
+        return changed
+    }
+
+    @GuardedBy("currentFavorites")
+    private fun favoritesAsListLocked(): List<ControlInfo> {
+        return currentFavorites.flatMap { it.value.values }
+    }
+
+    override fun subscribeToFavorites() {
+        if (!available) {
+            Log.d(TAG, "Controls not available")
+            return
+        }
+        // Make a copy of the favorites list
+        val favorites = synchronized(currentFavorites) {
+            currentFavorites.flatMap { it.value.values.toList() }
+        }
+        bindingController.subscribe(favorites)
+    }
+
+    override fun unsubscribe() {
+        if (!available) {
+            Log.d(TAG, "Controls not available")
+            return
+        }
+        bindingController.unsubscribe()
+    }
+
+    override fun changeFavoriteStatus(controlInfo: ControlInfo, state: Boolean) {
+        if (!available) {
+            Log.d(TAG, "Controls not available")
+            return
+        }
+        var changed = false
+        val listOfControls = synchronized(currentFavorites) {
+            if (state) {
+                if (controlInfo.component !in currentFavorites) {
+                    currentFavorites.put(controlInfo.component, ArrayMap<String, ControlInfo>())
+                    changed = true
+                }
+                val controlsForComponent = currentFavorites.getValue(controlInfo.component)
+                if (controlInfo.controlId !in controlsForComponent) {
+                    controlsForComponent.put(controlInfo.controlId, controlInfo)
+                    changed = true
+                } else {
+                    if (controlsForComponent.getValue(controlInfo.controlId) != controlInfo) {
+                        controlsForComponent.put(controlInfo.controlId, controlInfo)
+                        changed = true
+                    }
+                }
+            } else {
+                changed = currentFavorites.get(controlInfo.component)
+                        ?.remove(controlInfo.controlId) != null
+            }
+            favoritesAsListLocked()
+        }
+        if (changed) {
+            persistenceWrapper.storeFavorites(listOfControls)
+        }
+    }
+
+    override fun refreshStatus(componentName: ComponentName, controls: List<Control>) {
+        if (!available) {
+            Log.d(TAG, "Controls not available")
+            return
+        }
+        executor.execute {
+            synchronized(currentFavorites) {
+                val changed = updateFavoritesLocked(componentName, controls)
+                if (changed) {
+                    persistenceWrapper.storeFavorites(favoritesAsListLocked())
+                }
+            }
+        }
+        uiController.onRefreshState(componentName, controls)
+    }
+
+    override fun onActionResponse(componentName: ComponentName, controlId: String, response: Int) {
+        if (!available) {
+            Log.d(TAG, "Controls not available")
+            return
+        }
+        uiController.onActionResponse(componentName, controlId, response)
+    }
+
+    override fun getFavoriteControls(): List<ControlInfo> {
+        if (!available) {
+            Log.d(TAG, "Controls not available")
+            return emptyList()
+        }
+        synchronized(currentFavorites) {
+            return favoritesAsListLocked()
+        }
+    }
+
+    override fun action(controlInfo: ControlInfo, action: ControlAction) {
+        bindingController.action(controlInfo, action)
+    }
+
+    override fun clearFavorites() {
+        val changed = synchronized(currentFavorites) {
+            currentFavorites.isNotEmpty().also {
+                currentFavorites.clear()
+            }
+        }
+        if (changed) {
+            persistenceWrapper.storeFavorites(emptyList())
+        }
+    }
+
+    override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
+        pw.println("ControlsController state:")
+        pw.println("  Favorites:")
+        synchronized(currentFavorites) {
+            currentFavorites.forEach {
+                it.value.forEach {
+                    pw.println("    ${it.value}")
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt
new file mode 100644
index 0000000..6f2d71f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.controller
+
+import android.app.ActivityManager
+import android.content.ComponentName
+import android.util.AtomicFile
+import android.util.Log
+import android.util.Slog
+import android.util.Xml
+import com.android.systemui.util.concurrency.DelayableExecutor
+import libcore.io.IoUtils
+import org.xmlpull.v1.XmlPullParser
+import org.xmlpull.v1.XmlPullParserException
+import java.io.File
+import java.io.FileInputStream
+import java.io.FileNotFoundException
+import java.io.IOException
+
+class ControlsFavoritePersistenceWrapper(
+    val file: File,
+    val executor: DelayableExecutor
+) {
+
+    companion object {
+        private const val TAG = "ControlsFavoritePersistenceWrapper"
+        const val FILE_NAME = "controls_favorites.xml"
+        private const val TAG_CONTROLS = "controls"
+        private const val TAG_CONTROL = "control"
+        private const val TAG_COMPONENT = "component"
+        private const val TAG_ID = "id"
+        private const val TAG_TITLE = "title"
+        private const val TAG_TYPE = "type"
+    }
+
+    val currentUser: Int
+        get() = ActivityManager.getCurrentUser()
+
+    fun storeFavorites(list: List<ControlInfo>) {
+        executor.execute {
+            val atomicFile = AtomicFile(file)
+            val writer = try {
+                atomicFile.startWrite()
+            } catch (e: IOException) {
+                Log.e(TAG, "Failed to start write file", e)
+                return@execute
+            }
+            try {
+                Xml.newSerializer().apply {
+                    setOutput(writer, "utf-8")
+                    setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true)
+                    startDocument(null, true)
+                    startTag(null, TAG_CONTROLS)
+                    list.forEach {
+                        startTag(null, TAG_CONTROL)
+                        attribute(null, TAG_COMPONENT, it.component.flattenToString())
+                        attribute(null, TAG_ID, it.controlId)
+                        attribute(null, TAG_TITLE, it.controlTitle.toString())
+                        attribute(null, TAG_TYPE, it.deviceType.toString())
+                        endTag(null, TAG_CONTROL)
+                    }
+                    endTag(null, TAG_CONTROLS)
+                    endDocument()
+                    atomicFile.finishWrite(writer)
+                }
+            } catch (t: Throwable) {
+                Log.e(TAG, "Failed to write file, reverting to previous version")
+                atomicFile.failWrite(writer)
+            } finally {
+                IoUtils.closeQuietly(writer)
+            }
+        }
+    }
+
+    fun readFavorites(): List<ControlInfo> {
+        if (!file.exists()) {
+            Log.d(TAG, "No favorites, returning empty list")
+            return emptyList()
+        }
+        val reader = try {
+            FileInputStream(file)
+        } catch (fnfe: FileNotFoundException) {
+            Slog.i(TAG, "No file found")
+            return emptyList()
+        }
+        try {
+            val parser = Xml.newPullParser()
+            parser.setInput(reader, null)
+            return parseXml(parser)
+        } catch (e: XmlPullParserException) {
+            throw IllegalStateException("Failed parsing favorites file: $file", e)
+        } catch (e: IOException) {
+            throw IllegalStateException("Failed parsing favorites file: $file", e)
+        } finally {
+            IoUtils.closeQuietly(reader)
+        }
+    }
+
+    private fun parseXml(parser: XmlPullParser): List<ControlInfo> {
+        var type: Int = 0
+        val infos = mutableListOf<ControlInfo>()
+        while (parser.next().also { type = it } != XmlPullParser.END_DOCUMENT) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue
+            }
+            val tagName = parser.name
+            if (tagName == TAG_CONTROL) {
+                val component = ComponentName.unflattenFromString(
+                        parser.getAttributeValue(null, TAG_COMPONENT))
+                val id = parser.getAttributeValue(null, TAG_ID)
+                val title = parser.getAttributeValue(null, TAG_TITLE)
+                val type = parser.getAttributeValue(null, TAG_TYPE)?.toInt()
+                if (component != null && id != null && title != null && type != null) {
+                    infos.add(ControlInfo(component, id, title, type))
+                }
+            }
+        }
+        return infos
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
new file mode 100644
index 0000000..79057ad
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.controller
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.os.Binder
+import android.os.Bundle
+import android.os.IBinder
+import android.os.RemoteException
+import android.service.controls.Control
+import android.service.controls.ControlsProviderService.CALLBACK_BINDER
+import android.service.controls.ControlsProviderService.CALLBACK_BUNDLE
+import android.service.controls.ControlsProviderService.CALLBACK_TOKEN
+import android.service.controls.IControlsProvider
+import android.service.controls.IControlsProviderCallback
+import android.service.controls.actions.ControlAction
+import android.util.ArraySet
+import android.util.Log
+import com.android.internal.annotations.GuardedBy
+import com.android.systemui.util.concurrency.DelayableExecutor
+import java.util.concurrent.TimeUnit
+
+typealias LoadCallback = (List<Control>) -> Unit
+class ControlsProviderLifecycleManager(
+    private val context: Context,
+    private val executor: DelayableExecutor,
+    private val serviceCallback: IControlsProviderCallback.Stub,
+    val componentName: ComponentName
+) : IBinder.DeathRecipient {
+
+    var lastLoadCallback: LoadCallback? = null
+        private set
+    val token: IBinder = Binder()
+    private var unbindImmediate = false
+    private var requiresBound = false
+    private var isBound = false
+    @GuardedBy("queuedMessages")
+    private val queuedMessages: MutableSet<Message> = ArraySet()
+    private var wrapper: ControlsProviderServiceWrapper? = null
+    private var bindTryCount = 0
+    private val TAG = javaClass.simpleName
+    private var onLoadCanceller: Runnable? = null
+
+    companion object {
+        private const val MSG_LOAD = 0
+        private const val MSG_SUBSCRIBE = 1
+        private const val MSG_UNSUBSCRIBE = 2
+        private const val MSG_ON_ACTION = 3
+        private const val MSG_UNBIND = 4
+        private const val BIND_RETRY_DELAY = 1000L // ms
+        private const val LOAD_TIMEOUT = 5000L // ms
+        private const val MAX_BIND_RETRIES = 5
+        private const val DEBUG = true
+        private val BIND_FLAGS = Context.BIND_AUTO_CREATE or Context.BIND_FOREGROUND_SERVICE or
+                Context.BIND_WAIVE_PRIORITY
+    }
+
+    private val intent = Intent().apply {
+        component = componentName
+        putExtra(CALLBACK_BUNDLE, Bundle().apply {
+            putBinder(CALLBACK_BINDER, serviceCallback)
+            putBinder(CALLBACK_TOKEN, token)
+        })
+    }
+
+    private fun bindService(bind: Boolean) {
+        requiresBound = bind
+        if (bind) {
+            if (bindTryCount == MAX_BIND_RETRIES) {
+                return
+            }
+            if (DEBUG) {
+                Log.d(TAG, "Binding service $intent")
+            }
+            bindTryCount++
+            try {
+                isBound = context.bindService(intent, serviceConnection, BIND_FLAGS)
+            } catch (e: SecurityException) {
+                Log.e(TAG, "Failed to bind to service", e)
+                isBound = false
+            }
+        } else {
+            if (DEBUG) {
+                Log.d(TAG, "Unbinding service $intent")
+            }
+            bindTryCount = 0
+            wrapper = null
+            if (isBound) {
+                context.unbindService(serviceConnection)
+                isBound = false
+            }
+        }
+    }
+
+    fun bindPermanently() {
+        unbindImmediate = false
+        unqueueMessage(Message.Unbind)
+        bindService(true)
+    }
+
+    private val serviceConnection = object : ServiceConnection {
+        override fun onServiceConnected(name: ComponentName, service: IBinder) {
+            if (DEBUG) Log.d(TAG, "onServiceConnected $name")
+            bindTryCount = 0
+            wrapper = ControlsProviderServiceWrapper(IControlsProvider.Stub.asInterface(service))
+            try {
+                service.linkToDeath(this@ControlsProviderLifecycleManager, 0)
+            } catch (_: RemoteException) {}
+            handlePendingMessages()
+        }
+
+        override fun onServiceDisconnected(name: ComponentName?) {
+            if (DEBUG) Log.d(TAG, "onServiceDisconnected $name")
+            isBound = false
+            bindService(false)
+        }
+    }
+
+    private fun handlePendingMessages() {
+        val queue = synchronized(queuedMessages) {
+            ArraySet(queuedMessages).also {
+                queuedMessages.clear()
+            }
+        }
+        if (Message.Unbind in queue) {
+            bindService(false)
+            return
+        }
+        if (Message.Load in queue) {
+            load()
+        }
+        queue.filter { it is Message.Subscribe }.flatMap { (it as Message.Subscribe).list }.run {
+            subscribe(this)
+        }
+        queue.filter { it is Message.Action }.forEach {
+            val msg = it as Message.Action
+            onAction(msg.id, msg.action)
+        }
+    }
+
+    override fun binderDied() {
+        if (wrapper == null) return
+        wrapper = null
+        if (requiresBound) {
+            if (DEBUG) {
+                Log.d(TAG, "binderDied")
+            }
+            // Try rebinding some time later
+        }
+    }
+
+    private fun queueMessage(message: Message) {
+        synchronized(queuedMessages) {
+            queuedMessages.add(message)
+        }
+    }
+
+    private fun unqueueMessage(message: Message) {
+        synchronized(queuedMessages) {
+            queuedMessages.removeIf { it.type == message.type }
+        }
+    }
+
+    private fun load() {
+        if (DEBUG) {
+            Log.d(TAG, "load $componentName")
+        }
+        if (!(wrapper?.load() ?: false)) {
+            queueMessage(Message.Load)
+            binderDied()
+        }
+    }
+
+    fun maybeBindAndLoad(callback: LoadCallback) {
+        unqueueMessage(Message.Unbind)
+        lastLoadCallback = callback
+        onLoadCanceller = executor.executeDelayed({
+            // Didn't receive a response in time, log and send back empty list
+            Log.d(TAG, "Timeout waiting onLoad for $componentName")
+            serviceCallback.onLoad(token, emptyList())
+        }, LOAD_TIMEOUT, TimeUnit.MILLISECONDS)
+        if (isBound) {
+            load()
+        } else {
+            queueMessage(Message.Load)
+            unbindImmediate = true
+            bindService(true)
+        }
+    }
+
+    fun maybeBindAndSubscribe(controlIds: List<String>) {
+        if (isBound) {
+            subscribe(controlIds)
+        } else {
+            queueMessage(Message.Subscribe(controlIds))
+            bindService(true)
+        }
+    }
+
+    private fun subscribe(controlIds: List<String>) {
+        if (DEBUG) {
+            Log.d(TAG, "subscribe $componentName - $controlIds")
+        }
+        if (!(wrapper?.subscribe(controlIds) ?: false)) {
+            queueMessage(Message.Subscribe(controlIds))
+            binderDied()
+        }
+    }
+
+    fun maybeBindAndSendAction(controlId: String, action: ControlAction) {
+        if (isBound) {
+            onAction(controlId, action)
+        } else {
+            queueMessage(Message.Action(controlId, action))
+            bindService(true)
+        }
+    }
+
+    private fun onAction(controlId: String, action: ControlAction) {
+        if (DEBUG) {
+            Log.d(TAG, "onAction $componentName - $controlId")
+        }
+        if (!(wrapper?.onAction(controlId, action) ?: false)) {
+            queueMessage(Message.Action(controlId, action))
+            binderDied()
+        }
+    }
+
+    fun unsubscribe() {
+        if (DEBUG) {
+            Log.d(TAG, "unsubscribe $componentName")
+        }
+        unqueueMessage(Message.Subscribe(emptyList())) // Removes all subscribe messages
+        wrapper?.unsubscribe()
+    }
+
+    fun maybeUnbindAndRemoveCallback() {
+        lastLoadCallback = null
+        onLoadCanceller?.run()
+        onLoadCanceller = null
+        if (unbindImmediate) {
+            bindService(false)
+        }
+    }
+
+    fun unbindService() {
+        unbindImmediate = true
+        maybeUnbindAndRemoveCallback()
+    }
+
+    sealed class Message {
+        abstract val type: Int
+        object Load : Message() {
+            override val type = MSG_LOAD
+        }
+        object Unbind : Message() {
+            override val type = MSG_UNBIND
+        }
+        class Subscribe(val list: List<String>) : Message() {
+            override val type = MSG_SUBSCRIBE
+        }
+        class Action(val id: String, val action: ControlAction) : Message() {
+            override val type = MSG_ON_ACTION
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapper.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapper.kt
new file mode 100644
index 0000000..882a10d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapper.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.controller
+
+import android.service.controls.actions.ControlAction
+import android.service.controls.IControlsProvider
+import android.util.Log
+
+class ControlsProviderServiceWrapper(val service: IControlsProvider) {
+    companion object {
+        private const val TAG = "ControlsProviderServiceWrapper"
+    }
+
+    private fun callThroughService(block: () -> Unit): Boolean {
+        try {
+            block()
+            return true
+        } catch (ex: Exception) {
+            Log.d(TAG, "Caught exception from ControlsProviderService", ex)
+            return false
+        }
+    }
+
+    fun load(): Boolean {
+        return callThroughService {
+            service.load()
+        }
+    }
+
+    fun subscribe(controlIds: List<String>): Boolean {
+        return callThroughService {
+            service.subscribe(controlIds)
+        }
+    }
+
+    fun unsubscribe(): Boolean {
+        return callThroughService {
+            service.unsubscribe()
+        }
+    }
+
+    fun onAction(controlId: String, action: ControlAction): Boolean {
+        return callThroughService {
+            service.onAction(controlId, action)
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
new file mode 100644
index 0000000..859311e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.dagger
+
+import android.app.Activity
+import com.android.systemui.controls.controller.ControlsBindingController
+import com.android.systemui.controls.controller.ControlsBindingControllerImpl
+import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.controls.controller.ControlsControllerImpl
+import com.android.systemui.controls.controller.ControlsFavoritePersistenceWrapper
+import com.android.systemui.controls.management.ControlsFavoritingActivity
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.management.ControlsListingControllerImpl
+import com.android.systemui.controls.management.ControlsProviderSelectorActivity
+import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.controls.ui.ControlsUiControllerImpl
+import dagger.Binds
+import dagger.BindsOptionalOf
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+abstract class ControlsModule {
+
+    @Binds
+    abstract fun provideControlsListingController(
+        controller: ControlsListingControllerImpl
+    ): ControlsListingController
+
+    @Binds
+    abstract fun provideControlsController(controller: ControlsControllerImpl): ControlsController
+
+    @Binds
+    abstract fun provideControlsBindingController(
+        controller: ControlsBindingControllerImpl
+    ): ControlsBindingController
+
+    @Binds
+    abstract fun provideUiController(controller: ControlsUiControllerImpl): ControlsUiController
+
+    @BindsOptionalOf
+    abstract fun optionalPersistenceWrapper(): ControlsFavoritePersistenceWrapper
+
+    @Binds
+    @IntoMap
+    @ClassKey(ControlsProviderSelectorActivity::class)
+    abstract fun provideControlsProviderActivity(
+        activity: ControlsProviderSelectorActivity
+    ): Activity
+
+    @Binds
+    @IntoMap
+    @ClassKey(ControlsFavoritingActivity::class)
+    abstract fun provideControlsFavoritingActivity(
+        activity: ControlsFavoritingActivity
+    ): Activity
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
new file mode 100644
index 0000000..d62bb4d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.management
+
+import android.content.ComponentName
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.lifecycle.Lifecycle
+import androidx.recyclerview.widget.RecyclerView
+import com.android.settingslib.widget.CandidateInfo
+import com.android.systemui.R
+import java.util.concurrent.Executor
+
+/**
+ * Adapter for binding [CandidateInfo] related to [ControlsProviderService].
+ *
+ * This class handles subscribing and keeping track of the list of valid applications for
+ * displaying.
+ *
+ * @param uiExecutor an executor on the view thread of the containing [RecyclerView]
+ * @param lifecycle the lifecycle of the containing [LifecycleOwner] to control listening status
+ * @param controlsListingController the controller to keep track of valid applications
+ * @param layoutInflater an inflater for the views in the containing [RecyclerView]
+ * @param onAppSelected a callback to indicate that an app has been selected in the list.
+ */
+class AppAdapter(
+    uiExecutor: Executor,
+    lifecycle: Lifecycle,
+    controlsListingController: ControlsListingController,
+    private val layoutInflater: LayoutInflater,
+    private val onAppSelected: (ComponentName?) -> Unit = {}
+) : RecyclerView.Adapter<AppAdapter.Holder>() {
+
+    private var listOfServices = emptyList<CandidateInfo>()
+
+    private val callback = object : ControlsListingController.ControlsListingCallback {
+        override fun onServicesUpdated(list: List<CandidateInfo>) {
+            uiExecutor.execute {
+                listOfServices = list
+                notifyDataSetChanged()
+            }
+        }
+    }
+
+    init {
+        controlsListingController.observe(lifecycle, callback)
+    }
+
+    override fun onCreateViewHolder(parent: ViewGroup, i: Int): Holder {
+        return Holder(layoutInflater.inflate(R.layout.app_item, parent, false))
+    }
+
+    override fun getItemCount() = listOfServices.size
+
+    override fun onBindViewHolder(holder: Holder, index: Int) {
+        holder.bindData(listOfServices[index])
+        holder.itemView.setOnClickListener {
+            onAppSelected(ComponentName.unflattenFromString(listOfServices[index].key))
+        }
+    }
+
+    /**
+     * Holder for binding views in the [RecyclerView]-
+     */
+    class Holder(view: View) : RecyclerView.ViewHolder(view) {
+        private val icon: ImageView = itemView.requireViewById(com.android.internal.R.id.icon)
+        private val title: TextView = itemView.requireViewById(com.android.internal.R.id.title)
+
+        /**
+         * Bind data to the view
+         * @param data Information about the [ControlsProviderService] to bind to the data
+         */
+        fun bindData(data: CandidateInfo) {
+            icon.setImageDrawable(data.loadIcon())
+            title.text = data.loadLabel()
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
new file mode 100644
index 0000000..e6d3c26
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.management
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.CheckBox
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.android.systemui.R
+import com.android.systemui.controls.ControlStatus
+import com.android.systemui.controls.controller.ControlInfo
+
+/**
+ * Adapter for binding [Control] information to views.
+ *
+ * @param layoutInflater an inflater for the views in the containing [RecyclerView]
+ * @param favoriteCallback a callback to be called when the favorite status of a [Control] is
+ *                         changed. The callback will take a [ControlInfo.Builder] that's
+ *                         pre-populated with the [Control] information and the new favorite
+ *                         status.
+ */
+class ControlAdapter(
+    private val layoutInflater: LayoutInflater,
+    private val favoriteCallback: (ControlInfo.Builder, Boolean) -> Unit
+) : RecyclerView.Adapter<ControlAdapter.Holder>() {
+
+    var listOfControls = emptyList<ControlStatus>()
+
+    override fun onCreateViewHolder(parent: ViewGroup, i: Int): Holder {
+        return Holder(layoutInflater.inflate(R.layout.control_item, parent, false))
+    }
+
+    override fun getItemCount() = listOfControls.size
+
+    override fun onBindViewHolder(holder: Holder, index: Int) {
+        holder.bindData(listOfControls[index], favoriteCallback)
+    }
+
+    /**
+     * Holder for binding views in the [RecyclerView]-
+     */
+    class Holder(view: View) : RecyclerView.ViewHolder(view) {
+        private val title: TextView = itemView.requireViewById(R.id.title)
+        private val subtitle: TextView = itemView.requireViewById(R.id.subtitle)
+        private val favorite: CheckBox = itemView.requireViewById(R.id.favorite)
+
+        /**
+         * Bind data to the view
+         * @param data information about the [Control]
+         * @param callback a callback to be called when the favorite status of the [Control] is
+         *                 changed. The callback will take a [ControlInfo.Builder] that's
+         *                 pre-populated with the [Control] information and the new favorite status.
+         */
+        fun bindData(data: ControlStatus, callback: (ControlInfo.Builder, Boolean) -> Unit) {
+            title.text = data.control.title
+            subtitle.text = data.control.subtitle
+            favorite.isChecked = data.favorite
+            favorite.setOnClickListener {
+                val infoBuilder = ControlInfo.Builder().apply {
+                    controlId = data.control.controlId
+                    controlTitle = data.control.title
+                    deviceType = data.control.deviceType
+                }
+                callback(infoBuilder, favorite.isChecked)
+            }
+        }
+    }
+
+    fun setItems(list: List<ControlStatus>) {
+        listOfControls = list
+        notifyDataSetChanged()
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
new file mode 100644
index 0000000..01c4fef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.management
+
+import android.app.Activity
+import android.content.ComponentName
+import android.os.Bundle
+import android.view.LayoutInflater
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.android.systemui.controls.controller.ControlInfo
+import com.android.systemui.controls.controller.ControlsControllerImpl
+import com.android.systemui.dagger.qualifiers.Main
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+class ControlsFavoritingActivity @Inject constructor(
+    @Main private val executor: Executor,
+    private val controller: ControlsControllerImpl
+) : Activity() {
+
+    companion object {
+        private const val TAG = "ControlsFavoritingActivity"
+        const val EXTRA_APP = "extra_app_label"
+        const val EXTRA_COMPONENT = "extra_component"
+    }
+
+    private lateinit var recyclerView: RecyclerView
+    private lateinit var adapter: ControlAdapter
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        val app = intent.getCharSequenceExtra(EXTRA_APP)
+        val component = intent.getParcelableExtra<ComponentName>(EXTRA_COMPONENT)
+
+        // If we have no component name, there's not much we can do.
+        val callback = component?.let {
+            { infoBuilder: ControlInfo.Builder, status: Boolean ->
+                infoBuilder.componentName = it
+                controller.changeFavoriteStatus(infoBuilder.build(), status)
+            }
+        } ?: { _, _ -> Unit }
+
+        recyclerView = RecyclerView(applicationContext)
+        adapter = ControlAdapter(LayoutInflater.from(applicationContext), callback)
+        recyclerView.adapter = adapter
+        recyclerView.layoutManager = LinearLayoutManager(applicationContext)
+
+        if (app != null) {
+            setTitle("Controls for $app")
+        } else {
+            setTitle("Controls")
+        }
+        setContentView(recyclerView)
+
+        component?.let {
+            controller.loadForComponent(it) {
+                executor.execute {
+                    adapter.setItems(it)
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt
new file mode 100644
index 0000000..09e0ce9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.management
+
+import android.content.ComponentName
+import com.android.settingslib.widget.CandidateInfo
+import com.android.systemui.statusbar.policy.CallbackController
+
+interface ControlsListingController :
+        CallbackController<ControlsListingController.ControlsListingCallback> {
+
+    fun getCurrentServices(): List<CandidateInfo>
+    fun getAppLabel(name: ComponentName): CharSequence? = ""
+
+    interface ControlsListingCallback {
+        fun onServicesUpdated(list: List<CandidateInfo>)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
new file mode 100644
index 0000000..9372162
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.management
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.ServiceInfo
+import android.service.controls.ControlsProviderService
+import android.util.Log
+import com.android.internal.annotations.VisibleForTesting
+import com.android.settingslib.applications.DefaultAppInfo
+import com.android.settingslib.applications.ServiceListing
+import com.android.settingslib.widget.CandidateInfo
+import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.dagger.qualifiers.Background
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/**
+ * Provides a listing of components to be used as ControlsServiceProvider.
+ *
+ * This controller keeps track of components that satisfy:
+ *
+ * * Has an intent-filter responding to [ControlsProviderService.CONTROLS_ACTION]
+ * * Has the bind permission `android.permission.BIND_CONTROLS`
+ */
+@Singleton
+class ControlsListingControllerImpl @VisibleForTesting constructor(
+    private val context: Context,
+    @Background private val backgroundExecutor: Executor,
+    private val serviceListing: ServiceListing
+) : ControlsListingController {
+
+    @Inject
+    constructor(context: Context, executor: Executor): this(
+            context,
+            executor,
+            ServiceListing.Builder(context)
+                    .setIntentAction(ControlsProviderService.CONTROLS_ACTION)
+                    .setPermission("android.permission.BIND_CONTROLS")
+                    .setNoun("Controls Provider")
+                    .setSetting("controls_providers")
+                    .setTag("controls_providers")
+                    .build()
+    )
+
+    companion object {
+        private const val TAG = "ControlsListingControllerImpl"
+    }
+
+    private var availableServices = emptyList<ServiceInfo>()
+
+    init {
+        serviceListing.addCallback {
+            Log.d(TAG, "ServiceConfig reloaded")
+            availableServices = it.toList()
+
+            backgroundExecutor.execute {
+                callbacks.forEach {
+                    it.onServicesUpdated(getCurrentServices())
+                }
+            }
+        }
+    }
+
+    // All operations in background thread
+    private val callbacks = mutableSetOf<ControlsListingController.ControlsListingCallback>()
+
+    /**
+     * Adds a callback to this controller.
+     *
+     * The callback will be notified after it is added as well as any time that the valid
+     * components change.
+     *
+     * @param listener a callback to be notified
+     */
+    override fun addCallback(listener: ControlsListingController.ControlsListingCallback) {
+        backgroundExecutor.execute {
+            callbacks.add(listener)
+            if (callbacks.size == 1) {
+                serviceListing.setListening(true)
+                serviceListing.reload()
+            } else {
+                listener.onServicesUpdated(getCurrentServices())
+            }
+        }
+    }
+
+    /**
+     * Removes a callback from this controller.
+     *
+     * @param listener the callback to be removed.
+     */
+    override fun removeCallback(listener: ControlsListingController.ControlsListingCallback) {
+        backgroundExecutor.execute {
+            callbacks.remove(listener)
+            if (callbacks.size == 0) {
+                serviceListing.setListening(false)
+            }
+        }
+    }
+
+    /**
+     * @return a list of components that satisfy the requirements to be a
+     *         [ControlsProviderService]
+     */
+    override fun getCurrentServices(): List<CandidateInfo> =
+            availableServices.map { ControlsServiceInfo(context, it) }
+
+    /**
+     * Get the localized label for the component.
+     *
+     * @param name the name of the component
+     * @return a label as returned by [CandidateInfo.loadLabel] or `null`.
+     */
+    override fun getAppLabel(name: ComponentName): CharSequence? {
+        return getCurrentServices().firstOrNull { (it as? DefaultAppInfo)?.componentName == name }
+                ?.loadLabel()
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
new file mode 100644
index 0000000..69af516
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.management
+
+import android.content.ComponentName
+import android.content.Intent
+import android.os.Bundle
+import android.view.LayoutInflater
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.util.LifecycleActivity
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * Activity to select an application to favorite the [Control] provided by them.
+ */
+class ControlsProviderSelectorActivity @Inject constructor(
+    @Main private val executor: Executor,
+    private val listingController: ControlsListingController
+) : LifecycleActivity() {
+
+    companion object {
+        private const val TAG = "ControlsProviderSelectorActivity"
+    }
+
+    private lateinit var recyclerView: RecyclerView
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        recyclerView = RecyclerView(applicationContext)
+        recyclerView.adapter = AppAdapter(executor, lifecycle, listingController,
+                LayoutInflater.from(this), ::launchFavoritingActivity)
+        recyclerView.layoutManager = LinearLayoutManager(applicationContext)
+
+        setContentView(recyclerView)
+    }
+
+    /**
+     * Launch the [ControlsFavoritingActivity] for the specified component.
+     * @param component a component name for a [ControlsProviderService]
+     */
+    fun launchFavoritingActivity(component: ComponentName?) {
+        component?.let {
+            val intent = Intent(applicationContext, ControlsFavoritingActivity::class.java).apply {
+                putExtra(ControlsFavoritingActivity.EXTRA_APP, listingController.getAppLabel(it))
+                putExtra(ControlsFavoritingActivity.EXTRA_COMPONENT, it)
+                flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP
+            }
+            startActivity(intent)
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
new file mode 100644
index 0000000..0270c2b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.ui
+
+import android.content.ComponentName
+import android.service.controls.Control
+import android.service.controls.actions.ControlAction
+
+interface ControlsUiController {
+    fun onRefreshState(componentName: ComponentName, controls: List<Control>)
+    fun onActionResponse(
+        componentName: ComponentName,
+        controlId: String,
+        @ControlAction.ResponseResult response: Int
+    )
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
new file mode 100644
index 0000000..0ace126
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.ui
+
+import android.content.ComponentName
+import android.service.controls.Control
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class ControlsUiControllerImpl @Inject constructor() : ControlsUiController {
+
+    override fun onRefreshState(componentName: ComponentName, controls: List<Control>) {
+        TODO("not implemented")
+    }
+
+    override fun onActionResponse(componentName: ComponentName, controlId: String, response: Int) {
+        TODO("not implemented")
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index df79310..9598e3c 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -20,6 +20,7 @@
 
 import com.android.systemui.ForegroundServicesDialog;
 import com.android.systemui.keyguard.WorkLockActivity;
+import com.android.systemui.screenrecord.ScreenRecordDialog;
 import com.android.systemui.settings.BrightnessDialog;
 import com.android.systemui.tuner.TunerActivity;
 
@@ -29,7 +30,7 @@
 import dagger.multibindings.IntoMap;
 
 /**
- * Services and Activities that are injectable should go here.
+ * Activities that are injectable should go here.
  */
 @Module
 public abstract class DefaultActivityBinder {
@@ -56,4 +57,10 @@
     @IntoMap
     @ClassKey(BrightnessDialog.class)
     public abstract Activity bindBrightnessDialog(BrightnessDialog activity);
+
+    /** Inject into ScreenRecordDialog */
+    @Binds
+    @IntoMap
+    @ClassKey(ScreenRecordDialog.class)
+    public abstract Activity bindScreenRecordDialog(ScreenRecordDialog activity);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
index f790d99..f006acf 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
@@ -22,6 +22,7 @@
 import com.android.systemui.SystemUIService;
 import com.android.systemui.doze.DozeService;
 import com.android.systemui.keyguard.KeyguardService;
+import com.android.systemui.screenrecord.RecordingService;
 import com.android.systemui.screenshot.TakeScreenshotService;
 
 import dagger.Binds;
@@ -63,4 +64,10 @@
     @IntoMap
     @ClassKey(TakeScreenshotService.class)
     public abstract Service bindTakeScreenshotService(TakeScreenshotService service);
+
+    /** Inject into RecordingService */
+    @Binds
+    @IntoMap
+    @ClassKey(RecordingService.class)
+    public abstract Service bindRecordingService(RecordingService service);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
index 20917bd..2877ed0 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
@@ -20,6 +20,7 @@
 import com.android.systemui.appops.AppOpsController;
 import com.android.systemui.appops.AppOpsControllerImpl;
 import com.android.systemui.classifier.FalsingManagerProxy;
+import com.android.systemui.controls.dagger.ControlsModule;
 import com.android.systemui.doze.DozeHost;
 import com.android.systemui.globalactions.GlobalActionsComponent;
 import com.android.systemui.globalactions.GlobalActionsImpl;
@@ -85,7 +86,7 @@
 /**
  * Maps interfaces to implementations for use with Dagger.
  */
-@Module
+@Module(includes = {ControlsModule.class})
 public abstract class DependencyBinder {
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index f06c849..2b53727 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -42,6 +42,7 @@
 import com.android.systemui.qs.tiles.NfcTile;
 import com.android.systemui.qs.tiles.NightDisplayTile;
 import com.android.systemui.qs.tiles.RotationLockTile;
+import com.android.systemui.qs.tiles.ScreenRecordTile;
 import com.android.systemui.qs.tiles.UiModeNightTile;
 import com.android.systemui.qs.tiles.UserTile;
 import com.android.systemui.qs.tiles.WifiTile;
@@ -77,6 +78,7 @@
     private final Provider<NfcTile> mNfcTileProvider;
     private final Provider<GarbageMonitor.MemoryTile> mMemoryTileProvider;
     private final Provider<UiModeNightTile> mUiModeNightTileProvider;
+    private final Provider<ScreenRecordTile> mScreenRecordTileProvider;
 
     private QSTileHost mHost;
 
@@ -100,7 +102,8 @@
             Provider<NightDisplayTile> nightDisplayTileProvider,
             Provider<NfcTile> nfcTileProvider,
             Provider<GarbageMonitor.MemoryTile> memoryTileProvider,
-            Provider<UiModeNightTile> uiModeNightTileProvider) {
+            Provider<UiModeNightTile> uiModeNightTileProvider,
+            Provider<ScreenRecordTile> screenRecordTileProvider) {
         mWifiTileProvider = wifiTileProvider;
         mBluetoothTileProvider = bluetoothTileProvider;
         mControlsTileProvider = controlsTileProvider;
@@ -121,6 +124,7 @@
         mNfcTileProvider = nfcTileProvider;
         mMemoryTileProvider = memoryTileProvider;
         mUiModeNightTileProvider = uiModeNightTileProvider;
+        mScreenRecordTileProvider = screenRecordTileProvider;
     }
 
     public void setHost(QSTileHost host) {
@@ -179,6 +183,8 @@
                 return mNfcTileProvider.get();
             case "dark":
                 return mUiModeNightTileProvider.get();
+            case "screenrecord":
+                return mScreenRecordTileProvider.get();
         }
 
         // Custom tiles
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
new file mode 100644
index 0000000..596c3b9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles;
+
+import android.content.Intent;
+import android.service.quicksettings.Tile;
+import android.util.Log;
+
+import com.android.systemui.R;
+import com.android.systemui.plugins.qs.QSTile;
+import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.screenrecord.RecordingController;
+
+import javax.inject.Inject;
+
+/**
+ * Quick settings tile for screen recording
+ */
+public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> {
+    private static final String TAG = "ScreenRecordTile";
+    private RecordingController mController;
+    private long mMillisUntilFinished = 0;
+
+    @Inject
+    public ScreenRecordTile(QSHost host, RecordingController controller) {
+        super(host);
+        mController = controller;
+    }
+
+    @Override
+    public BooleanState newTileState() {
+        return new BooleanState();
+    }
+
+    @Override
+    protected void handleClick() {
+        if (mController.isStarting()) {
+            cancelCountdown();
+        } else if (mController.isRecording()) {
+            stopRecording();
+        } else {
+            startCountdown();
+        }
+        refreshState();
+    }
+
+    /**
+     * Refresh tile state
+     * @param millisUntilFinished Time until countdown completes, or 0 if not counting down
+     */
+    public void refreshState(long millisUntilFinished) {
+        mMillisUntilFinished = millisUntilFinished;
+        refreshState();
+    }
+
+    @Override
+    protected void handleUpdateState(BooleanState state, Object arg) {
+        boolean isStarting = mController.isStarting();
+        boolean isRecording = mController.isRecording();
+
+        state.label = mContext.getString(R.string.quick_settings_screen_record_label);
+        state.value = isRecording || isStarting;
+        state.state = (isRecording || isStarting) ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
+        state.handlesLongClick = false;
+
+        if (isRecording) {
+            state.icon = ResourceIcon.get(R.drawable.ic_qs_screenrecord);
+            state.secondaryLabel = mContext.getString(R.string.quick_settings_screen_record_stop);
+        } else if (isStarting) {
+            // round, since the timer isn't exact
+            int countdown = (int) Math.floorDiv(mMillisUntilFinished + 500, 1000);
+            // TODO update icon
+            state.icon = ResourceIcon.get(R.drawable.ic_qs_screenrecord);
+            state.secondaryLabel = String.format("%d...", countdown);
+        } else {
+            // TODO update icon
+            state.icon = ResourceIcon.get(R.drawable.ic_qs_screenrecord);
+            state.secondaryLabel = mContext.getString(R.string.quick_settings_screen_record_start);
+        }
+    }
+
+    @Override
+    public int getMetricsCategory() {
+        return 0;
+    }
+
+    @Override
+    public Intent getLongClickIntent() {
+        return null;
+    }
+
+    @Override
+    protected void handleSetListening(boolean listening) {
+    }
+
+    @Override
+    public CharSequence getTileLabel() {
+        return mContext.getString(R.string.quick_settings_screen_record_label);
+    }
+
+    private void startCountdown() {
+        Log.d(TAG, "Starting countdown");
+        // Close QS, otherwise the permission dialog appears beneath it
+        getHost().collapsePanels();
+        mController.launchRecordPrompt(this);
+    }
+
+    private void cancelCountdown() {
+        Log.d(TAG, "Cancelling countdown");
+        mController.cancelCountdown();
+    }
+
+    private void stopRecording() {
+        Log.d(TAG, "Stopping recording from tile");
+        mController.stopRecording();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
new file mode 100644
index 0000000..188501e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenrecord;
+
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.CountDownTimer;
+import android.util.Log;
+
+import com.android.systemui.qs.tiles.ScreenRecordTile;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Helper class to initiate a screen recording
+ */
+@Singleton
+public class RecordingController {
+    private static final String TAG = "RecordingController";
+    private static final String SYSUI_PACKAGE = "com.android.systemui";
+    private static final String SYSUI_SCREENRECORD_LAUNCHER =
+            "com.android.systemui.screenrecord.ScreenRecordDialog";
+
+    private final Context mContext;
+    private boolean mIsStarting;
+    private boolean mIsRecording;
+    private ScreenRecordTile mTileToUpdate;
+    private PendingIntent mStopIntent;
+    private CountDownTimer mCountDownTimer = null;
+
+    /**
+     * Create a new RecordingController
+     * @param context Context for the controller
+     */
+    @Inject
+    public RecordingController(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Show dialog of screen recording options to user.
+     */
+    public void launchRecordPrompt(ScreenRecordTile tileToUpdate) {
+        final ComponentName launcherComponent = new ComponentName(SYSUI_PACKAGE,
+                SYSUI_SCREENRECORD_LAUNCHER);
+        final Intent intent = new Intent();
+        intent.setComponent(launcherComponent);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra("com.android.systemui.screenrecord.EXTRA_SETTINGS_ONLY", true);
+        mContext.startActivity(intent);
+
+        mTileToUpdate = tileToUpdate;
+    }
+
+    /**
+     * Start counting down in preparation to start a recording
+     * @param ms Time in ms to count down
+     * @param startIntent Intent to start a recording
+     * @param stopIntent Intent to stop a recording
+     */
+    public void startCountdown(long ms, PendingIntent startIntent, PendingIntent stopIntent) {
+        mIsStarting = true;
+        mStopIntent = stopIntent;
+
+        mCountDownTimer = new CountDownTimer(ms, 1000) {
+            @Override
+            public void onTick(long millisUntilFinished) {
+                refreshTile(millisUntilFinished);
+            }
+
+            @Override
+            public void onFinish() {
+                mIsStarting = false;
+                mIsRecording = true;
+                refreshTile();
+                try {
+                    startIntent.send();
+                } catch (PendingIntent.CanceledException e) {
+                    Log.e(TAG, "Pending intent was cancelled: " + e.getMessage());
+                }
+            }
+        };
+
+        mCountDownTimer.start();
+    }
+
+    private void refreshTile() {
+        refreshTile(0);
+    }
+
+    private void refreshTile(long millisUntilFinished) {
+        if (mTileToUpdate != null) {
+            mTileToUpdate.refreshState(millisUntilFinished);
+        } else {
+            Log.e(TAG, "No tile to refresh");
+        }
+    }
+
+    /**
+     * Cancel a countdown in progress. This will not stop the recording if it already started.
+     */
+    public void cancelCountdown() {
+        if (mCountDownTimer != null) {
+            mCountDownTimer.cancel();
+        } else {
+            Log.e(TAG, "Timer was null");
+        }
+        mIsStarting = false;
+        refreshTile();
+    }
+
+    /**
+     * Check if the recording is currently counting down to begin
+     * @return
+     */
+    public boolean isStarting() {
+        return mIsStarting;
+    }
+
+    /**
+     * Check if the recording is ongoing
+     * @return
+     */
+    public boolean isRecording() {
+        return mIsRecording;
+    }
+
+    /**
+     * Stop the recording
+     */
+    public void stopRecording() {
+        updateState(false);
+        try {
+            mStopIntent.send();
+        } catch (PendingIntent.CanceledException e) {
+            Log.e(TAG, "Error stopping: " + e.getMessage());
+        }
+        refreshTile();
+    }
+
+    /**
+     * Update the current status
+     * @param isRecording
+     */
+    public void updateState(boolean isRecording) {
+        mIsRecording = isRecording;
+        refreshTile();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index 77c3ad9..1b32168 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -53,10 +53,14 @@
 import java.text.SimpleDateFormat;
 import java.util.Date;
 
+import javax.inject.Inject;
+
 /**
  * A service which records the device screen and optionally microphone input.
  */
 public class RecordingService extends Service {
+    public static final int REQUEST_CODE = 2;
+
     private static final int NOTIFICATION_ID = 1;
     private static final String TAG = "RecordingService";
     private static final String CHANNEL_ID = "screen_record";
@@ -65,7 +69,6 @@
     private static final String EXTRA_PATH = "extra_path";
     private static final String EXTRA_USE_AUDIO = "extra_useAudio";
     private static final String EXTRA_SHOW_TAPS = "extra_showTaps";
-    private static final int REQUEST_CODE = 2;
 
     private static final String ACTION_START = "com.android.systemui.screenrecord.START";
     private static final String ACTION_STOP = "com.android.systemui.screenrecord.STOP";
@@ -81,6 +84,7 @@
     private static final int AUDIO_BIT_RATE = 16;
     private static final int AUDIO_SAMPLE_RATE = 44100;
 
+    private final RecordingController mController;
     private MediaProjectionManager mMediaProjectionManager;
     private MediaProjection mMediaProjection;
     private Surface mInputSurface;
@@ -92,6 +96,11 @@
     private boolean mShowTaps;
     private File mTempFile;
 
+    @Inject
+    public RecordingService(RecordingController controller) {
+        mController = controller;
+    }
+
     /**
      * Get an intent to start the recording service.
      *
@@ -272,6 +281,7 @@
                     null);
 
             mMediaRecorder.start();
+            mController.updateState(true);
         } catch (IOException e) {
             Log.e(TAG, "Error starting screen recording: " + e.getMessage());
             e.printStackTrace();
@@ -285,7 +295,7 @@
         NotificationChannel channel = new NotificationChannel(
                 CHANNEL_ID,
                 getString(R.string.screenrecord_name),
-                NotificationManager.IMPORTANCE_HIGH);
+                NotificationManager.IMPORTANCE_LOW);
         channel.setDescription(getString(R.string.screenrecord_channel_description));
         channel.enableVibration(true);
         NotificationManager notificationManager =
@@ -399,6 +409,7 @@
         mInputSurface.release();
         mVirtualDisplay.release();
         stopSelf();
+        mController.updateState(false);
     }
 
     private void saveRecording(NotificationManager notificationManager) {
@@ -439,7 +450,12 @@
                 Settings.System.SHOW_TOUCHES, value);
     }
 
-    private static Intent getStopIntent(Context context) {
+    /**
+     * Get an intent to stop the recording service.
+     * @param context Context from the requesting activity
+     * @return
+     */
+    public static Intent getStopIntent(Context context) {
         return new Intent(context, RecordingService.class).setAction(ACTION_STOP);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
index 27e9fba..8324986 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
@@ -18,55 +18,41 @@
 
 import android.Manifest;
 import android.app.Activity;
+import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.media.projection.MediaProjectionManager;
 import android.os.Bundle;
-import android.util.Log;
-import android.widget.Button;
-import android.widget.CheckBox;
 import android.widget.Toast;
 
 import com.android.systemui.R;
 
+import javax.inject.Inject;
+
 /**
  * Activity to select screen recording options
  */
 public class ScreenRecordDialog extends Activity {
-    private static final String TAG = "ScreenRecord";
     private static final int REQUEST_CODE_VIDEO_ONLY = 200;
     private static final int REQUEST_CODE_VIDEO_TAPS = 201;
     private static final int REQUEST_CODE_PERMISSIONS = 299;
     private static final int REQUEST_CODE_VIDEO_AUDIO = 300;
     private static final int REQUEST_CODE_VIDEO_AUDIO_TAPS = 301;
     private static final int REQUEST_CODE_PERMISSIONS_AUDIO = 399;
-    private boolean mUseAudio;
-    private boolean mShowTaps;
+    private static final long DELAY_MS = 3000;
+
+    private final RecordingController mController;
+
+    @Inject
+    public ScreenRecordDialog(RecordingController controller) {
+        mController = controller;
+    }
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        setContentView(R.layout.screen_record_dialog);
-
-        final CheckBox micCheckBox = findViewById(R.id.checkbox_mic);
-        final CheckBox tapsCheckBox = findViewById(R.id.checkbox_taps);
-
-        final Button recordButton = findViewById(R.id.record_button);
-        recordButton.setOnClickListener(v -> {
-            mUseAudio = micCheckBox.isChecked();
-            mShowTaps = tapsCheckBox.isChecked();
-            Log.d(TAG, "Record button clicked: audio " + mUseAudio + ", taps " + mShowTaps);
-
-            if (mUseAudio && checkSelfPermission(Manifest.permission.RECORD_AUDIO)
-                    != PackageManager.PERMISSION_GRANTED) {
-                Log.d(TAG, "Requesting permission for audio");
-                requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO},
-                        REQUEST_CODE_PERMISSIONS_AUDIO);
-            } else {
-                requestScreenCapture();
-            }
-        });
+        requestScreenCapture();
     }
 
     private void requestScreenCapture() {
@@ -74,18 +60,23 @@
                 Context.MEDIA_PROJECTION_SERVICE);
         Intent permissionIntent = mediaProjectionManager.createScreenCaptureIntent();
 
-        if (mUseAudio) {
+        // TODO get saved settings
+        boolean useAudio = false;
+        boolean showTaps = false;
+        if (useAudio) {
             startActivityForResult(permissionIntent,
-                    mShowTaps ? REQUEST_CODE_VIDEO_AUDIO_TAPS : REQUEST_CODE_VIDEO_AUDIO);
+                    showTaps ? REQUEST_CODE_VIDEO_AUDIO_TAPS : REQUEST_CODE_VIDEO_AUDIO);
         } else {
             startActivityForResult(permissionIntent,
-                    mShowTaps ? REQUEST_CODE_VIDEO_TAPS : REQUEST_CODE_VIDEO_ONLY);
+                    showTaps ? REQUEST_CODE_VIDEO_TAPS : REQUEST_CODE_VIDEO_ONLY);
         }
     }
 
     @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-        mShowTaps = (requestCode == REQUEST_CODE_VIDEO_TAPS
+        boolean showTaps = (requestCode == REQUEST_CODE_VIDEO_TAPS
+                || requestCode == REQUEST_CODE_VIDEO_AUDIO_TAPS);
+        boolean useAudio = (requestCode == REQUEST_CODE_VIDEO_AUDIO
                 || requestCode == REQUEST_CODE_VIDEO_AUDIO_TAPS);
         switch (requestCode) {
             case REQUEST_CODE_VIDEO_TAPS:
@@ -93,11 +84,17 @@
             case REQUEST_CODE_VIDEO_ONLY:
             case REQUEST_CODE_VIDEO_AUDIO:
                 if (resultCode == RESULT_OK) {
-                    mUseAudio = (requestCode == REQUEST_CODE_VIDEO_AUDIO
-                            || requestCode == REQUEST_CODE_VIDEO_AUDIO_TAPS);
-                    startForegroundService(
-                            RecordingService.getStartIntent(this, resultCode, data, mUseAudio,
-                                    mShowTaps));
+                    PendingIntent startIntent = PendingIntent.getForegroundService(
+                            this, RecordingService.REQUEST_CODE, RecordingService.getStartIntent(
+                                    ScreenRecordDialog.this, resultCode, data, useAudio,
+                                    showTaps),
+                            PendingIntent.FLAG_UPDATE_CURRENT
+                            );
+                    PendingIntent stopIntent = PendingIntent.getService(
+                            this, RecordingService.REQUEST_CODE,
+                            RecordingService.getStopIntent(this),
+                            PendingIntent.FLAG_UPDATE_CURRENT);
+                    mController.startCountdown(DELAY_MS, startIntent, stopIntent);
                 } else {
                     Toast.makeText(this,
                             getResources().getString(R.string.screenrecord_permission_error),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
index 73bfe25..eaa9d78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
@@ -16,42 +16,135 @@
 
 package com.android.systemui.statusbar.notification.collection;
 
+import java.util.Arrays;
 import java.util.List;
 
-
 /**
  * Utility class for dumping the results of a {@link ShadeListBuilder} to a debug string.
  */
 public class ListDumper {
 
-    /** See class description */
-    public static String dumpList(List<ListEntry> entries) {
+    /**
+     * Creates a debug string for a list of grouped notifications that will be printed
+     * in the order given in a tiered/tree structure.
+     * @param includeRecordKeeping whether to print out the Pluggables that caused the notification
+     *                             entry to be in its current state (ie: filter, lifeExtender)
+     */
+    public static String dumpTree(
+            List<ListEntry> entries,
+            boolean includeRecordKeeping,
+            String indent) {
         StringBuilder sb = new StringBuilder();
-        for (int i = 0; i < entries.size(); i++) {
-            ListEntry entry = entries.get(i);
-            dumpEntry(entry, Integer.toString(i), "", sb);
+        final String childEntryIndent = indent + INDENT;
+        for (int topEntryIndex = 0; topEntryIndex < entries.size(); topEntryIndex++) {
+            ListEntry entry = entries.get(topEntryIndex);
+            dumpEntry(entry,
+                    Integer.toString(topEntryIndex),
+                    indent,
+                    sb,
+                    true,
+                    includeRecordKeeping);
             if (entry instanceof GroupEntry) {
                 GroupEntry ge = (GroupEntry) entry;
-                for (int j = 0; j < ge.getChildren().size(); j++) {
-                    dumpEntry(
-                            ge.getChildren().get(j),
-                            Integer.toString(j),
-                            INDENT,
-                            sb);
+                List<NotificationEntry> children = ge.getChildren();
+                for (int childIndex = 0;  childIndex < children.size(); childIndex++) {
+                    dumpEntry(children.get(childIndex),
+                            Integer.toString(topEntryIndex) + "." + Integer.toString(childIndex),
+                            childEntryIndent,
+                            sb,
+                            true,
+                            includeRecordKeeping);
                 }
             }
         }
         return sb.toString();
     }
 
+    /**
+     * Creates a debug string for a flat list of notifications
+     * @param includeRecordKeeping whether to print out the Pluggables that caused the notification
+     *                             entry to be in its current state (ie: filter, lifeExtender)
+     */
+    public static String dumpList(
+            List<NotificationEntry> entries,
+            boolean includeRecordKeeping,
+            String indent) {
+        StringBuilder sb = new StringBuilder();
+        for (int j = 0; j < entries.size(); j++) {
+            dumpEntry(
+                    entries.get(j),
+                    Integer.toString(j),
+                    indent,
+                    sb,
+                    false,
+                    includeRecordKeeping);
+        }
+        return sb.toString();
+    }
+
     private static void dumpEntry(
-            ListEntry entry, String index, String indent, StringBuilder sb) {
+            ListEntry entry,
+            String index,
+            String indent,
+            StringBuilder sb,
+            boolean includeParent,
+            boolean includeRecordKeeping) {
         sb.append(indent)
                 .append("[").append(index).append("] ")
-                .append(entry.getKey())
-                .append(" (parent=")
-                .append(entry.getParent() != null ? entry.getParent().getKey() : null)
-                .append(")\n");
+                .append(entry.getKey());
+
+        if (includeParent) {
+            sb.append(" (parent=")
+                    .append(entry.getParent() != null ? entry.getParent().getKey() : null)
+                    .append(")");
+        }
+
+        if (entry.mNotifSection != null) {
+            sb.append(" sectionIndex=")
+                    .append(entry.getSection())
+                    .append(" sectionName=")
+                    .append(entry.mNotifSection.getName());
+        }
+
+        if (includeRecordKeeping) {
+            NotificationEntry notifEntry = entry.getRepresentativeEntry();
+            StringBuilder rksb = new StringBuilder();
+
+            if (!notifEntry.mLifetimeExtenders.isEmpty()) {
+                String[] lifetimeExtenderNames = new String[notifEntry.mLifetimeExtenders.size()];
+                for (int i = 0; i < lifetimeExtenderNames.length; i++) {
+                    lifetimeExtenderNames[i] = notifEntry.mLifetimeExtenders.get(i).getName();
+                }
+                rksb.append("lifetimeExtenders=")
+                        .append(Arrays.toString(lifetimeExtenderNames))
+                        .append(" ");
+            }
+
+            if (notifEntry.mExcludingFilter != null) {
+                rksb.append("filter=")
+                        .append(notifEntry.mExcludingFilter)
+                        .append(" ");
+            }
+
+            if (notifEntry.mNotifPromoter != null) {
+                rksb.append("promoter=")
+                        .append(notifEntry.mNotifPromoter)
+                        .append(" ");
+            }
+
+            if (notifEntry.hasInflationError()) {
+                rksb.append("hasInflationError ");
+            }
+
+            String rkString = rksb.toString();
+            if (!rkString.isEmpty()) {
+                sb.append("\n\t")
+                        .append(indent)
+                        .append(rkString);
+            }
+        }
+
+        sb.append("\n");
     }
 
     private static final String INDENT = "  ";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
index dc68c4b..56ad0e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
@@ -18,6 +18,8 @@
 
 import android.annotation.Nullable;
 
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
+
 /**
  * Abstract superclass for top-level entries, i.e. things that can appear in the final notification
  * list shown to users. In practice, this means either GroupEntries or NotificationEntries.
@@ -27,7 +29,9 @@
 
     @Nullable private GroupEntry mParent;
     @Nullable private GroupEntry mPreviousParent;
-    private int mSection;
+    @Nullable NotifSection mNotifSection;
+
+    private int mSection = -1;
     int mFirstAddedIteration = -1;
 
     ListEntry(String key) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index 4b15b7f..c488c6b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -47,6 +47,8 @@
 import android.util.Log;
 
 import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.DumpController;
+import com.android.systemui.Dumpable;
 import com.android.systemui.statusbar.notification.collection.coalescer.CoalescedEvent;
 import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer;
 import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer.BatchableNotificationHandler;
@@ -56,6 +58,8 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
 import com.android.systemui.util.Assert;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -92,7 +96,7 @@
  */
 @MainThread
 @Singleton
-public class NotifCollection {
+public class NotifCollection implements Dumpable {
     private final IStatusBarService mStatusBarService;
 
     private final Map<String, NotificationEntry> mNotificationSet = new ArrayMap<>();
@@ -107,9 +111,10 @@
     private boolean mAmDispatchingToOtherCode;
 
     @Inject
-    public NotifCollection(IStatusBarService statusBarService) {
+    public NotifCollection(IStatusBarService statusBarService, DumpController dumpController) {
         Assert.isMainThread();
         mStatusBarService = statusBarService;
+        dumpController.registerDumpable(TAG, this);
     }
 
     /** Initializes the NotifCollection and registers it to receive notification events. */
@@ -442,4 +447,19 @@
     public @interface CancellationReason {}
 
     public static final int REASON_UNKNOWN = 0;
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        final List<NotificationEntry> entries = new ArrayList<>(getActiveNotifs());
+
+        pw.println("\t" + TAG + " unsorted/unfiltered notifications:");
+        if (entries.size() == 0) {
+            pw.println("\t\t None");
+        }
+        pw.println(
+                ListDumper.dumpList(
+                        entries,
+                        true,
+                        "\t\t"));
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
index 7124517..0377f57 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
@@ -22,7 +22,7 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.SectionsProvider;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
 
@@ -48,7 +48,7 @@
  *   GroupEntry. These groups are then transformed in order to remove children or completely split
  *   them apart. To participate, see {@link #addPromoter}.
  * - Sorted: All top-level notifications are sorted. To participate, see
- *   {@link #setSectionsProvider} and {@link #setComparators}
+ *   {@link #setSections} and {@link #setComparators}
  *
  * The exact order of all hooks is as follows:
  *  0. Collection listeners are fired ({@link #addCollectionListener}).
@@ -58,7 +58,7 @@
  *  3. OnBeforeTransformGroupListeners are fired ({@link #addOnBeforeTransformGroupsListener})
  *  4. NotifPromoters are called on each notification with a parent ({@link #addPromoter})
  *  5. OnBeforeSortListeners are fired ({@link #addOnBeforeSortListener})
- *  6. SectionsProvider is called on each top-level entry in the list ({@link #setSectionsProvider})
+ *  6. Top-level entries are assigned sections by NotifSections ({@link #setSections})
  *  7. Top-level entries within the same section are sorted by NotifComparators
  *     ({@link #setComparators})
  *  8. Pre-render filters are fired on each notification ({@link #addPreRenderFilter})
@@ -142,14 +142,13 @@
     }
 
     /**
-     * Assigns sections to each top-level entry, where a section is simply an integer. Sections are
-     * the primary metric by which top-level entries are sorted; NotifComparators are only consulted
-     * when two entries are in the same section. The pipeline doesn't assign any particular meaning
-     * to section IDs -- from it's perspective they're just numbers and it sorts them by a simple
-     * numerical comparison.
+     * Sections that are used to sort top-level entries.  If two entries have the same section,
+     * NotifComparators are consulted. Sections from this list are called in order for each
+     * notification passed through the pipeline. The first NotifSection to return true for
+     * {@link NotifSection#isInSection(ListEntry)} sets the entry as part of its Section.
      */
-    public void setSectionsProvider(SectionsProvider provider) {
-        mShadeListBuilder.setSectionsProvider(provider);
+    public void setSections(List<NotifSection> sections) {
+        mShadeListBuilder.setSections(sections);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
index 3bbd722..820c042 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
@@ -176,12 +176,14 @@
                     }
                     entry.ranking = newRanking
 
-                    val oldSbn = entry.sbn.cloneLight()
                     val newOverrideGroupKey = newRanking.overrideGroupKey
-                    if (!Objects.equals(oldSbn.overrideGroupKey, newOverrideGroupKey)) {
+                    if (!Objects.equals(entry.sbn.overrideGroupKey, newOverrideGroupKey)) {
+                        val oldGroupKey = entry.sbn.groupKey
+                        val oldIsGroup = entry.sbn.isGroup
+                        val oldIsGroupSummary = entry.sbn.notification.isGroupSummary
                         entry.sbn.overrideGroupKey = newOverrideGroupKey
-                        // TODO: notify group manager here?
-                        groupManager.onEntryUpdated(entry, oldSbn)
+                        groupManager.onEntryUpdated(entry, oldGroupKey, oldIsGroup,
+                                oldIsGroupSummary)
                     }
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 76c524b..97f8ec5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.notification.collection;
 
 import static com.android.systemui.statusbar.notification.collection.GroupEntry.ROOT_ENTRY;
-import static com.android.systemui.statusbar.notification.collection.ListDumper.dumpList;
 import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_BUILD_STARTED;
 import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_FINALIZING;
 import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_GROUPING;
@@ -31,7 +30,10 @@
 import android.annotation.MainThread;
 import android.annotation.Nullable;
 import android.util.ArrayMap;
+import android.util.Pair;
 
+import com.android.systemui.DumpController;
+import com.android.systemui.Dumpable;
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener;
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener;
@@ -39,13 +41,15 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.SectionsProvider;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
 import com.android.systemui.statusbar.notification.logging.NotifEvent;
 import com.android.systemui.statusbar.notification.logging.NotifLog;
 import com.android.systemui.util.Assert;
 import com.android.systemui.util.time.SystemClock;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -63,7 +67,7 @@
  */
 @MainThread
 @Singleton
-public class ShadeListBuilder {
+public class ShadeListBuilder implements Dumpable {
     private final SystemClock mSystemClock;
     private final NotifLog mNotifLog;
 
@@ -79,7 +83,7 @@
     private final List<NotifPromoter> mNotifPromoters = new ArrayList<>();
     private final List<NotifFilter> mNotifPreRenderFilters = new ArrayList<>();
     private final List<NotifComparator> mNotifComparators = new ArrayList<>();
-    private SectionsProvider mSectionsProvider = new DefaultSectionsProvider();
+    private final List<NotifSection> mNotifSections = new ArrayList<>();
 
     private final List<OnBeforeTransformGroupsListener> mOnBeforeTransformGroupsListeners =
             new ArrayList<>();
@@ -92,10 +96,14 @@
     private final List<ListEntry> mReadOnlyNotifList = Collections.unmodifiableList(mNotifList);
 
     @Inject
-    public ShadeListBuilder(SystemClock systemClock, NotifLog notifLog) {
+    public ShadeListBuilder(
+            SystemClock systemClock,
+            NotifLog notifLog,
+            DumpController dumpController) {
         Assert.isMainThread();
         mSystemClock = systemClock;
         mNotifLog = notifLog;
+        dumpController.registerDumpable(TAG, this);
     }
 
     /**
@@ -163,12 +171,15 @@
         promoter.setInvalidationListener(this::onPromoterInvalidated);
     }
 
-    void setSectionsProvider(SectionsProvider provider) {
+    void setSections(List<NotifSection> sections) {
         Assert.isMainThread();
         mPipelineState.requireState(STATE_IDLE);
 
-        mSectionsProvider = provider;
-        provider.setInvalidationListener(this::onSectionsProviderInvalidated);
+        mNotifSections.clear();
+        for (NotifSection section : sections) {
+            mNotifSections.add(section);
+            section.setInvalidationListener(this::onNotifSectionInvalidated);
+        }
     }
 
     void setComparators(List<NotifComparator> comparators) {
@@ -223,12 +234,12 @@
         rebuildListIfBefore(STATE_TRANSFORMING);
     }
 
-    private void onSectionsProviderInvalidated(SectionsProvider provider) {
+    private void onNotifSectionInvalidated(NotifSection section) {
         Assert.isMainThread();
 
-        mNotifLog.log(NotifEvent.SECTIONS_PROVIDER_INVALIDATED, String.format(
-                "Sections provider \"%s\" invalidated; pipeline state is %d",
-                provider.getName(),
+        mNotifLog.log(NotifEvent.SECTION_INVALIDATED, String.format(
+                "Section \"%s\" invalidated; pipeline state is %d",
+                section.getName(),
                 mPipelineState.getState()));
 
         rebuildListIfBefore(STATE_SORTING);
@@ -311,7 +322,7 @@
         sortList();
 
         // Step 6: Filter out entries after pre-group filtering, grouping, promoting and sorting
-        // Now filters can see grouping information to determine whether to filter or not
+        // Now filters can see grouping information to determine whether to filter or not.
         mPipelineState.incrementTo(STATE_PRE_RENDER_FILTERING);
         filterNotifs(mNotifList, mNewNotifList, mNotifPreRenderFilters);
         applyNewNotifList();
@@ -324,7 +335,7 @@
 
         // Step 6: Dispatch the new list, first to any listeners and then to the view layer
         mNotifLog.log(NotifEvent.DISPATCH_FINAL_LIST, "List finalized, is:\n"
-                + dumpList(mNotifList));
+                + ListDumper.dumpTree(mNotifList, false, "\t\t"));
         dispatchOnBeforeRenderList(mReadOnlyNotifList);
         if (mOnRenderListListener != null) {
             mOnRenderListListener.onRenderList(mReadOnlyNotifList);
@@ -573,6 +584,8 @@
      * filtered out during any of the filtering steps.
      */
     private void annulAddition(ListEntry entry) {
+        entry.setSection(-1);
+        entry.mNotifSection = null;
         entry.setParent(null);
         if (entry.mFirstAddedIteration == mIterationCount) {
             entry.mFirstAddedIteration = -1;
@@ -582,11 +595,12 @@
     private void sortList() {
         // Assign sections to top-level elements and sort their children
         for (ListEntry entry : mNotifList) {
-            entry.setSection(mSectionsProvider.getSection(entry));
+            Pair<NotifSection, Integer> sectionWithIndex = applySections(entry);
             if (entry instanceof GroupEntry) {
                 GroupEntry parent = (GroupEntry) entry;
                 for (NotificationEntry child : parent.getChildren()) {
-                    child.setSection(0);
+                    child.mNotifSection = sectionWithIndex.first;
+                    child.setSection(sectionWithIndex.second);
                 }
                 parent.sortChildren(sChildComparator);
             }
@@ -747,6 +761,45 @@
         return null;
     }
 
+    private Pair<NotifSection, Integer> applySections(ListEntry entry) {
+        final Pair<NotifSection, Integer> sectionWithIndex = findSection(entry);
+        final NotifSection section = sectionWithIndex.first;
+        final Integer sectionIndex = sectionWithIndex.second;
+
+        if (section != entry.mNotifSection) {
+            if (entry.mNotifSection == null) {
+                mNotifLog.log(NotifEvent.SECTION_CHANGED, String.format(
+                        "%s: sectioned by '%s' [index=%d].",
+                        entry.getKey(),
+                        section.getName(),
+                        sectionIndex));
+            } else {
+                mNotifLog.log(NotifEvent.SECTION_CHANGED, String.format(
+                        "%s: section changed: '%s' [index=%d] -> '%s [index=%d]'.",
+                        entry.getKey(),
+                        entry.mNotifSection,
+                        entry.getSection(),
+                        section,
+                        sectionIndex));
+            }
+
+            entry.mNotifSection = section;
+            entry.setSection(sectionIndex);
+        }
+
+        return sectionWithIndex;
+    }
+
+    private Pair<NotifSection, Integer> findSection(ListEntry entry) {
+        for (int i = 0; i < mNotifSections.size(); i++) {
+            NotifSection sectioner = mNotifSections.get(i);
+            if (sectioner.isInSection(entry)) {
+                return new Pair<>(sectioner, i);
+            }
+        }
+        return new Pair<>(sDefaultSection, mNotifSections.size());
+    }
+
     private void rebuildListIfBefore(@PipelineState.StateName int state) {
         mPipelineState.requireIsBefore(state);
         if (mPipelineState.is(STATE_IDLE)) {
@@ -772,6 +825,19 @@
         }
     }
 
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("\t" + TAG + " shade notifications:");
+        if (getShadeList().size() == 0) {
+            pw.println("\t\t None");
+        }
+
+        pw.println(ListDumper.dumpTree(
+                getShadeList(),
+                true,
+                "\t\t"));
+    }
+
     /** See {@link #setOnRenderListListener(OnRenderListListener)} */
     public interface OnRenderListListener {
         /**
@@ -783,16 +849,13 @@
         void onRenderList(List<ListEntry> entries);
     }
 
-    private static class DefaultSectionsProvider extends SectionsProvider {
-        DefaultSectionsProvider() {
-            super("DefaultSectionsProvider");
-        }
-
-        @Override
-        public int getSection(ListEntry entry) {
-            return 0;
-        }
-    }
+    private static final NotifSection sDefaultSection =
+            new NotifSection("DefaultSection") {
+                @Override
+                public boolean isInSection(ListEntry entry) {
+                    return true;
+                }
+            };
 
     private static final String TAG = "NotifListBuilderImpl";
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/Coordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/Coordinator.java
index c1a11b2..d8b2e40 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/Coordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/Coordinator.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.collection.coordinator;
 
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
 
 /**
@@ -28,4 +29,8 @@
      * Coordinators should register their listeners and {@link Pluggable}s to the pipeline.
      */
     void attach(NotifPipeline pipeline);
+
+    default NotifSection getSection() {
+        return null;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
index 562a618..8d0dd5b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
@@ -16,9 +16,11 @@
 
 package com.android.systemui.statusbar.notification.collection.coordinator;
 
+import com.android.systemui.DumpController;
 import com.android.systemui.Dumpable;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
@@ -39,18 +41,21 @@
 public class NotifCoordinators implements Dumpable {
     private static final String TAG = "NotifCoordinators";
     private final List<Coordinator> mCoordinators = new ArrayList<>();
-
+    private final List<NotifSection> mOrderedSections = new ArrayList<>();
     /**
      * Creates all the coordinators.
      */
     @Inject
     public NotifCoordinators(
+            DumpController dumpController,
             FeatureFlags featureFlags,
             KeyguardCoordinator keyguardCoordinator,
             RankingCoordinator rankingCoordinator,
             ForegroundCoordinator foregroundCoordinator,
             DeviceProvisionedCoordinator deviceProvisionedCoordinator,
             PreparationCoordinator preparationCoordinator) {
+        dumpController.registerDumpable(TAG, this);
+
         mCoordinators.add(keyguardCoordinator);
         mCoordinators.add(rankingCoordinator);
         mCoordinators.add(foregroundCoordinator);
@@ -59,6 +64,13 @@
             mCoordinators.add(preparationCoordinator);
         }
         // TODO: add new Coordinators here! (b/145134683, b/112656837)
+
+        // TODO: add the sections in a particular ORDER (HeadsUp < People < Alerting)
+        for (Coordinator c : mCoordinators) {
+            if (c.getSection() != null) {
+                mOrderedSections.add(c.getSection());
+            }
+        }
     }
 
     /**
@@ -69,6 +81,8 @@
         for (Coordinator c : mCoordinators) {
             c.attach(pipeline);
         }
+
+        pipeline.setSections(mOrderedSections);
     }
 
     @Override
@@ -78,5 +92,9 @@
         for (Coordinator c : mCoordinators) {
             pw.println("\t" + c.getClass());
         }
+
+        for (NotifSection s : mOrderedSections) {
+            pw.println("\t" + s.getName());
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index 1ab20a9..5cd3e94 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -41,6 +41,9 @@
 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
+import com.android.systemui.statusbar.notification.people.NotificationPersonExtractorPluginBoundary;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifierImpl;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder;
@@ -49,6 +52,7 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.policy.ExtensionControllerImpl;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
 import java.util.Objects;
@@ -66,7 +70,6 @@
     private final NotificationGroupManager mGroupManager;
     private final NotificationGutsManager mGutsManager;
     private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
-
     private final Context mContext;
     private final NotificationRowContentBinder mRowContentBinder;
     private final NotificationMessagingUtil mMessagingUtil;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSection.java
new file mode 100644
index 0000000..fe5ba3c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSection.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.listbuilder.pluggable;
+
+import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.ShadeListBuilder;
+
+/**
+ * Pluggable for participating in notif sectioning. See {@link ShadeListBuilder#setSections}.
+ */
+public abstract class NotifSection extends Pluggable<NotifSection> {
+    protected NotifSection(String name) {
+        super(name);
+    }
+
+    /**
+     * If returns true, this notification is considered within this section.
+     * Sectioning is performed on each top level notification each time the pipeline is run.
+     * However, this doesn't necessarily mean that your section will get called on each top-level
+     * notification. The first section to return true determines the section of the notification.
+     */
+    public abstract boolean isInSection(ListEntry entry);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/SectionsProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/SectionsProvider.java
deleted file mode 100644
index 11ea850..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/SectionsProvider.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.listbuilder.pluggable;
-
-import com.android.systemui.statusbar.notification.collection.ListEntry;
-
-/**
- * Interface for sorting notifications into "sections", such as a heads-upping section, people
- * section, alerting section, silent section, etc.
- */
-public abstract class SectionsProvider extends Pluggable<SectionsProvider> {
-
-    protected SectionsProvider(String name) {
-        super(name);
-    }
-
-    /**
-     * Returns the section that this entry belongs to. A section can be any non-negative integer.
-     * When entries are sorted, they are first sorted by section and then by any remainining
-     * comparators.
-     */
-    public abstract int getSection(ListEntry entry);
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
index 02acc81..2374cde 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
@@ -94,7 +94,7 @@
             LIST_BUILD_COMPLETE,
             PRE_GROUP_FILTER_INVALIDATED,
             PROMOTER_INVALIDATED,
-            SECTIONS_PROVIDER_INVALIDATED,
+            SECTION_INVALIDATED,
             COMPARATOR_INVALIDATED,
             PARENT_CHANGED,
             FILTER_CHANGED,
@@ -132,12 +132,13 @@
                     "ListBuildComplete",
                     "FilterInvalidated",
                     "PromoterInvalidated",
-                    "SectionsProviderInvalidated",
+                    "SectionInvalidated",
                     "ComparatorInvalidated",
                     "ParentChanged",
                     "FilterChanged",
                     "PromoterChanged",
                     "FinalFilterInvalidated",
+                    "SectionerChanged",
 
                     // NEM event labels:
                     "NotifAdded",
@@ -170,13 +171,14 @@
     public static final int LIST_BUILD_COMPLETE = 4;
     public static final int PRE_GROUP_FILTER_INVALIDATED = 5;
     public static final int PROMOTER_INVALIDATED = 6;
-    public static final int SECTIONS_PROVIDER_INVALIDATED = 7;
+    public static final int SECTION_INVALIDATED = 7;
     public static final int COMPARATOR_INVALIDATED = 8;
     public static final int PARENT_CHANGED = 9;
     public static final int FILTER_CHANGED = 10;
     public static final int PROMOTER_CHANGED = 11;
     public static final int PRE_RENDER_FILTER_INVALIDATED = 12;
-    private static final int TOTAL_LIST_BUILDER_EVENT_TYPES = 13;
+    public static final int SECTION_CHANGED = 13;
+    private static final int TOTAL_LIST_BUILDER_EVENT_TYPES = 14;
 
     /**
      * Events related to {@link NotificationEntryManager}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index a8a35d0..b71beda 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -1150,6 +1150,7 @@
         mMenuRow = plugin;
         if (mMenuRow.shouldUseDefaultMenuItems()) {
             ArrayList<MenuItem> items = new ArrayList<>();
+            items.add(NotificationMenuRow.createConversationItem(mContext));
             items.add(NotificationMenuRow.createInfoItem(mContext));
             items.add(NotificationMenuRow.createSnoozeItem(mContext));
             items.add(NotificationMenuRow.createAppOpsItem(mContext));
@@ -1163,7 +1164,7 @@
     @Override
     public void onPluginDisconnected(NotificationMenuRowPlugin plugin) {
         boolean existed = mMenuRow.getMenuView() != null;
-        mMenuRow = new NotificationMenuRow(mContext); // Back to default
+        mMenuRow = new NotificationMenuRow(mContext);
         if (existed) {
             createMenu();
         }
@@ -1720,6 +1721,8 @@
      */
     public void reset() {
         mShowingPublicInitialized = false;
+        unDismiss();
+        resetTranslation();
         onHeightReset();
         requestLayout();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
new file mode 100644
index 0000000..ec420f3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -0,0 +1,633 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.notification.row;
+
+import static android.app.Notification.EXTRA_IS_GROUP_CONVERSATION;
+import static android.app.NotificationChannel.PLACEHOLDER_CONVERSATION_ID;
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;
+
+import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_BUBBLE;
+import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_FAVORITE;
+import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_HOME;
+import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_MUTE;
+import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_SNOOZE;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.app.INotificationManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Slog;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+import java.lang.annotation.Retention;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * The guts of a conversation notification revealed when performing a long press.
+ */
+public class NotificationConversationInfo extends LinearLayout implements
+        NotificationGuts.GutsContent {
+    private static final String TAG = "ConversationGuts";
+
+
+    private INotificationManager mINotificationManager;
+    private LauncherApps mLauncherApps;
+    ShortcutManager mShortcutManager;
+    private PackageManager mPm;
+    private VisualStabilityManager mVisualStabilityManager;
+
+    private String mPackageName;
+    private String mAppName;
+    private int mAppUid;
+    private String mDelegatePkg;
+    private NotificationChannel mNotificationChannel;
+    private ShortcutInfo mShortcutInfo;
+    private String mConversationId;
+    private NotificationEntry mEntry;
+    private StatusBarNotification mSbn;
+    private boolean mIsDeviceProvisioned;
+
+    private int mStartingChannelImportance;
+    private boolean mStartedAsBubble;
+    private boolean mIsBubbleable;
+    // TODO: remove when launcher api works
+    @VisibleForTesting
+    boolean mShowHomeScreen = false;
+
+    private @UpdateChannelRunnable.Action int mSelectedAction = -1;
+
+    private OnSnoozeClickListener mOnSnoozeClickListener;
+    private OnSettingsClickListener mOnSettingsClickListener;
+    private OnAppSettingsClickListener mAppSettingsClickListener;
+    private NotificationGuts mGutsContainer;
+    private BubbleController mBubbleController;
+
+    @VisibleForTesting
+    boolean mSkipPost = false;
+
+    private OnClickListener mOnBubbleClick = v -> {
+        mSelectedAction = ACTION_BUBBLE;
+        if (mStartedAsBubble) {
+            mBubbleController.onUserDemotedBubbleFromNotification(mEntry);
+        } else {
+            mBubbleController.onUserCreatedBubbleFromNotification(mEntry);
+        }
+        closeControls(v, true);
+    };
+
+    private OnClickListener mOnHomeClick = v -> {
+        mSelectedAction = ACTION_HOME;
+        mShortcutManager.requestPinShortcut(mShortcutInfo, null);
+        closeControls(v, true);
+    };
+
+    private OnClickListener mOnFavoriteClick = v -> {
+        mSelectedAction = ACTION_FAVORITE;
+        closeControls(v, true);
+    };
+
+    private OnClickListener mOnSnoozeClick = v -> {
+        mSelectedAction = ACTION_SNOOZE;
+        mOnSnoozeClickListener.onClick(v, 1);
+        closeControls(v, true);
+    };
+
+    private OnClickListener mOnMuteClick = v -> {
+      mSelectedAction = ACTION_MUTE;
+      closeControls(v, true);
+    };
+
+    public NotificationConversationInfo(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public interface OnSettingsClickListener {
+        void onClick(View v, NotificationChannel channel, int appUid);
+    }
+
+    public interface OnAppSettingsClickListener {
+        void onClick(View v, Intent intent);
+    }
+
+    public interface OnSnoozeClickListener {
+        void onClick(View v, int hoursToSnooze);
+    }
+
+    public void bindNotification(
+            ShortcutManager shortcutManager,
+            LauncherApps launcherApps,
+            PackageManager pm,
+            INotificationManager iNotificationManager,
+            VisualStabilityManager visualStabilityManager,
+            String pkg,
+            NotificationChannel notificationChannel,
+            NotificationEntry entry,
+            OnSettingsClickListener onSettingsClick,
+            OnAppSettingsClickListener onAppSettingsClick,
+            OnSnoozeClickListener onSnoozeClickListener,
+            boolean isDeviceProvisioned) {
+        mSelectedAction = -1;
+        mINotificationManager = iNotificationManager;
+        mVisualStabilityManager = visualStabilityManager;
+        mBubbleController = Dependency.get(BubbleController.class);
+        mPackageName = pkg;
+        mEntry = entry;
+        mSbn = entry.getSbn();
+        mPm = pm;
+        mAppSettingsClickListener = onAppSettingsClick;
+        mAppName = mPackageName;
+        mOnSettingsClickListener = onSettingsClick;
+        mNotificationChannel = notificationChannel;
+        mStartingChannelImportance = mNotificationChannel.getImportance();
+        mAppUid = mSbn.getUid();
+        mDelegatePkg = mSbn.getOpPkg();
+        mIsDeviceProvisioned = isDeviceProvisioned;
+        mOnSnoozeClickListener = onSnoozeClickListener;
+
+        mShortcutManager = shortcutManager;
+        mLauncherApps = launcherApps;
+        mConversationId = mNotificationChannel.getConversationId();
+        if (TextUtils.isEmpty(mNotificationChannel.getConversationId())) {
+            mConversationId = mSbn.getNotification().getShortcutId();
+        }
+        // TODO: flag this when flag exists
+        if (TextUtils.isEmpty(mConversationId)) {
+            mConversationId = mSbn.getId() + mSbn.getTag() + PLACEHOLDER_CONVERSATION_ID;
+        }
+        // TODO: consider querying this earlier in the notification pipeline and passing it in
+        LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery()
+                .setPackage(mPackageName)
+                .setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED)
+                .setShortcutIds(Arrays.asList(mConversationId));
+        List<ShortcutInfo> shortcuts = mLauncherApps.getShortcuts(query, mSbn.getUser());
+        if (shortcuts != null && !shortcuts.isEmpty()) {
+            mShortcutInfo = shortcuts.get(0);
+        }
+
+        mIsBubbleable = mEntry.getBubbleMetadata() != null;
+        mStartedAsBubble = mEntry.isBubble();
+
+        createConversationChannelIfNeeded();
+
+        bindHeader();
+        bindActions();
+
+    }
+
+    void createConversationChannelIfNeeded() {
+        // If this channel is not already a customized conversation channel, create
+        // a custom channel
+        if (TextUtils.isEmpty(mNotificationChannel.getConversationId())) {
+            try {
+                // TODO: associate this key with this channel service side so the customization
+                // isn't forgotten on the next update
+                mINotificationManager.createConversationNotificationChannelForPackage(
+                        mPackageName, mAppUid, mNotificationChannel, mConversationId);
+                mNotificationChannel = mINotificationManager.getConversationNotificationChannel(
+                        mContext.getOpPackageName(), UserHandle.getUserId(mAppUid), mPackageName,
+                        mNotificationChannel.getId(), false, mConversationId);
+
+                // TODO: ask LA to pin the shortcut once api exists for pinning one shortcut at a
+                // time
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Could not create conversation channel", e);
+            }
+        }
+    }
+
+    private void bindActions() {
+        // TODO: figure out what should happen for non-configurable channels
+
+        Button bubble = findViewById(R.id.bubble);
+        bubble.setVisibility(mIsBubbleable ? VISIBLE : GONE);
+        bubble.setOnClickListener(mOnBubbleClick);
+        if (mStartedAsBubble) {
+            bubble.setText(R.string.notification_conversation_unbubble);
+        } else {
+            bubble.setText(R.string.notification_conversation_bubble);
+        }
+
+        Button home = findViewById(R.id.home);
+        home.setOnClickListener(mOnHomeClick);
+        home.setVisibility(mShowHomeScreen && mShortcutInfo != null
+                && mShortcutManager.isRequestPinShortcutSupported()
+                ? VISIBLE : GONE);
+
+        Button favorite = findViewById(R.id.fave);
+        favorite.setOnClickListener(mOnFavoriteClick);
+        if (mNotificationChannel.canBypassDnd()) {
+            favorite.setText(R.string.notification_conversation_unfavorite);
+            favorite.setCompoundDrawablesRelative(
+                    mContext.getDrawable(R.drawable.ic_star), null, null, null);
+        } else {
+            favorite.setText(R.string.notification_conversation_favorite);
+            favorite.setCompoundDrawablesRelative(
+                    mContext.getDrawable(R.drawable.ic_star_border), null, null, null);
+        }
+
+        Button snooze = findViewById(R.id.snooze);
+        snooze.setOnClickListener(mOnSnoozeClick);
+
+        Button mute = findViewById(R.id.mute);
+        mute.setOnClickListener(mOnMuteClick);
+        if (mStartingChannelImportance >= IMPORTANCE_DEFAULT
+                || mStartingChannelImportance == IMPORTANCE_UNSPECIFIED) {
+            mute.setText(R.string.notification_conversation_mute);
+            favorite.setCompoundDrawablesRelative(
+                    mContext.getDrawable(R.drawable.ic_notifications_silence), null, null, null);
+        } else {
+            mute.setText(R.string.notification_conversation_unmute);
+            favorite.setCompoundDrawablesRelative(
+                    mContext.getDrawable(R.drawable.ic_notifications_alert), null, null, null);
+        }
+
+    }
+
+    private void bindHeader() {
+        bindConversationDetails();
+
+        // Delegate
+        bindDelegate();
+
+        // Set up app settings link (i.e. Customize)
+        View settingsLinkView = findViewById(R.id.app_settings);
+        Intent settingsIntent = getAppSettingsIntent(mPm, mPackageName,
+                mNotificationChannel,
+                mSbn.getId(), mSbn.getTag());
+        if (settingsIntent != null
+                && !TextUtils.isEmpty(mSbn.getNotification().getSettingsText())) {
+            settingsLinkView.setVisibility(VISIBLE);
+            settingsLinkView.setOnClickListener((View view) -> {
+                mAppSettingsClickListener.onClick(view, settingsIntent);
+            });
+        } else {
+            settingsLinkView.setVisibility(View.GONE);
+        }
+
+        // System Settings button.
+        final View settingsButton = findViewById(R.id.info);
+        settingsButton.setOnClickListener(getSettingsOnClickListener());
+        settingsButton.setVisibility(settingsButton.hasOnClickListeners() ? VISIBLE : GONE);
+    }
+
+    private OnClickListener getSettingsOnClickListener() {
+        if (mAppUid >= 0 && mOnSettingsClickListener != null && mIsDeviceProvisioned) {
+            final int appUidF = mAppUid;
+            return ((View view) -> {
+                mOnSettingsClickListener.onClick(view, mNotificationChannel, appUidF);
+            });
+        }
+        return null;
+    }
+
+    private void bindConversationDetails() {
+        final TextView channelName = findViewById(R.id.parent_channel_name);
+        channelName.setText(mNotificationChannel.getName());
+
+        bindGroup();
+        bindName();
+        bindPackage();
+        bindIcon();
+
+    }
+
+    private void bindIcon() {
+        ImageView image = findViewById(R.id.conversation_icon);
+        if (mShortcutInfo != null) {
+            image.setImageDrawable(mLauncherApps.getShortcutBadgedIconDrawable(mShortcutInfo,
+                    mContext.getResources().getDisplayMetrics().densityDpi));
+        } else {
+            // TODO: flag this behavior
+            if (mSbn.getNotification().extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION, false)) {
+                // TODO: maybe use a generic group icon, or a composite of recent senders
+                image.setImageDrawable(mPm.getDefaultActivityIcon());
+            } else {
+                final List<Notification.MessagingStyle.Message> messages =
+                        Notification.MessagingStyle.Message.getMessagesFromBundleArray(
+                                (Parcelable[]) mSbn.getNotification().extras.get(
+                                        Notification.EXTRA_MESSAGES));
+
+                final Notification.MessagingStyle.Message latestMessage =
+                        Notification.MessagingStyle.findLatestIncomingMessage(messages);
+                Icon personIcon = latestMessage.getSenderPerson().getIcon();
+                if (personIcon != null) {
+                    image.setImageIcon(latestMessage.getSenderPerson().getIcon());
+                } else {
+                    // TODO: choose something better
+                    image.setImageDrawable(mPm.getDefaultActivityIcon());
+                }
+            }
+        }
+    }
+
+    private void bindName() {
+        TextView name = findViewById(R.id.name);
+        if (mShortcutInfo != null) {
+            name.setText(mShortcutInfo.getShortLabel());
+        } else {
+            // TODO: flag this behavior
+            Bundle extras = mSbn.getNotification().extras;
+            String nameString = extras.getString(Notification.EXTRA_CONVERSATION_TITLE);
+            if (TextUtils.isEmpty(nameString)) {
+                nameString = extras.getString(Notification.EXTRA_TITLE);
+            }
+            name.setText(nameString);
+        }
+    }
+
+    private void bindPackage() {
+        ApplicationInfo info;
+        try {
+            info = mPm.getApplicationInfo(
+                    mPackageName,
+                    PackageManager.MATCH_UNINSTALLED_PACKAGES
+                            | PackageManager.MATCH_DISABLED_COMPONENTS
+                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+                            | PackageManager.MATCH_DIRECT_BOOT_AWARE);
+            if (info != null) {
+                mAppName = String.valueOf(mPm.getApplicationLabel(info));
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+        }
+        ((TextView) findViewById(R.id.pkg_name)).setText(mAppName);
+    }
+
+    private void bindDelegate() {
+        TextView delegateView = findViewById(R.id.delegate_name);
+        TextView dividerView = findViewById(R.id.pkg_divider);
+
+        if (!TextUtils.equals(mPackageName, mDelegatePkg)) {
+            // this notification was posted by a delegate!
+            delegateView.setVisibility(View.VISIBLE);
+            dividerView.setVisibility(View.VISIBLE);
+        } else {
+            delegateView.setVisibility(View.GONE);
+            dividerView.setVisibility(View.GONE);
+        }
+    }
+
+    private void bindGroup() {
+        // Set group information if this channel has an associated group.
+        CharSequence groupName = null;
+        if (mNotificationChannel != null && mNotificationChannel.getGroup() != null) {
+            try {
+                final NotificationChannelGroup notificationChannelGroup =
+                        mINotificationManager.getNotificationChannelGroupForPackage(
+                                mNotificationChannel.getGroup(), mPackageName, mAppUid);
+                if (notificationChannelGroup != null) {
+                    groupName = notificationChannelGroup.getName();
+                }
+            } catch (RemoteException e) {
+            }
+        }
+        TextView groupNameView = findViewById(R.id.group_name);
+        View groupDivider = findViewById(R.id.group_divider);
+        if (groupName != null) {
+            groupNameView.setText(groupName);
+            groupNameView.setVisibility(VISIBLE);
+            groupDivider.setVisibility(VISIBLE);
+        } else {
+            groupNameView.setVisibility(GONE);
+            groupDivider.setVisibility(GONE);
+        }
+    }
+
+    @Override
+    public boolean post(Runnable action) {
+        if (mSkipPost) {
+            action.run();
+            return true;
+        } else {
+            return super.post(action);
+        }
+    }
+
+    @Override
+    public void onFinishedClosing() {
+        // TODO: do we need to do anything here?
+    }
+
+    @Override
+    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+        super.onInitializeAccessibilityEvent(event);
+        if (mGutsContainer != null &&
+                event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
+            if (mGutsContainer.isExposed()) {
+                event.getText().add(mContext.getString(
+                        R.string.notification_channel_controls_opened_accessibility, mAppName));
+            } else {
+                event.getText().add(mContext.getString(
+                        R.string.notification_channel_controls_closed_accessibility, mAppName));
+            }
+        }
+    }
+
+    private Intent getAppSettingsIntent(PackageManager pm, String packageName,
+            NotificationChannel channel, int id, String tag) {
+        Intent intent = new Intent(Intent.ACTION_MAIN)
+                .addCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES)
+                .setPackage(packageName);
+        final List<ResolveInfo> resolveInfos = pm.queryIntentActivities(
+                intent,
+                PackageManager.MATCH_DEFAULT_ONLY
+        );
+        if (resolveInfos == null || resolveInfos.size() == 0 || resolveInfos.get(0) == null) {
+            return null;
+        }
+        final ActivityInfo activityInfo = resolveInfos.get(0).activityInfo;
+        intent.setClassName(activityInfo.packageName, activityInfo.name);
+        if (channel != null) {
+            intent.putExtra(Notification.EXTRA_CHANNEL_ID, channel.getId());
+        }
+        intent.putExtra(Notification.EXTRA_NOTIFICATION_ID, id);
+        intent.putExtra(Notification.EXTRA_NOTIFICATION_TAG, tag);
+        return intent;
+    }
+
+    /**
+     * Closes the controls and commits the updated importance values (indirectly).
+     *
+     * <p><b>Note,</b> this will only get called once the view is dismissing. This means that the
+     * user does not have the ability to undo the action anymore.
+     */
+    @VisibleForTesting
+    void closeControls(View v, boolean save) {
+        int[] parentLoc = new int[2];
+        int[] targetLoc = new int[2];
+        mGutsContainer.getLocationOnScreen(parentLoc);
+        v.getLocationOnScreen(targetLoc);
+        final int centerX = v.getWidth() / 2;
+        final int centerY = v.getHeight() / 2;
+        final int x = targetLoc[0] - parentLoc[0] + centerX;
+        final int y = targetLoc[1] - parentLoc[1] + centerY;
+        mGutsContainer.closeControls(x, y, save, false /* force */);
+    }
+
+    @Override
+    public void setGutsParent(NotificationGuts guts) {
+        mGutsContainer = guts;
+    }
+
+    @Override
+    public boolean willBeRemoved() {
+        return false;
+    }
+
+    @Override
+    public boolean shouldBeSaved() {
+        return mSelectedAction > -1;
+    }
+
+    @Override
+    public View getContentView() {
+        return this;
+    }
+
+    @Override
+    public boolean handleCloseControls(boolean save, boolean force) {
+        if (save && mSelectedAction > -1) {
+            Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
+            bgHandler.post(
+                    new UpdateChannelRunnable(mINotificationManager, mPackageName,
+                            mAppUid, mSelectedAction, mNotificationChannel));
+            mVisualStabilityManager.temporarilyAllowReordering();
+        }
+        return false;
+    }
+
+    @Override
+    public int getActualHeight() {
+        return getHeight();
+    }
+
+    @VisibleForTesting
+    public boolean isAnimating() {
+        return false;
+    }
+
+    static class UpdateChannelRunnable implements Runnable {
+
+        @Retention(SOURCE)
+        @IntDef({ACTION_BUBBLE, ACTION_HOME, ACTION_FAVORITE, ACTION_SNOOZE, ACTION_MUTE,
+                ACTION_DEMOTE})
+        private @interface Action {}
+        static final int ACTION_BUBBLE = 0;
+        static final int ACTION_HOME = 1;
+        static final int ACTION_FAVORITE = 2;
+        static final int ACTION_SNOOZE = 3;
+        static final int ACTION_MUTE = 4;
+        static final int ACTION_DEMOTE = 5;
+
+        private final INotificationManager mINotificationManager;
+        private final String mAppPkg;
+        private final int mAppUid;
+        private  NotificationChannel mChannelToUpdate;
+        private final @Action int mAction;
+
+        public UpdateChannelRunnable(INotificationManager notificationManager,
+                String packageName, int appUid, @Action int action,
+                @NonNull NotificationChannel channelToUpdate) {
+            mINotificationManager = notificationManager;
+            mAppPkg = packageName;
+            mAppUid = appUid;
+            mChannelToUpdate = channelToUpdate;
+            mAction = action;
+        }
+
+        @Override
+        public void run() {
+            try {
+                switch (mAction) {
+                    case ACTION_BUBBLE:
+                        mChannelToUpdate.setAllowBubbles(!mChannelToUpdate.canBubble());
+                        break;
+                    case ACTION_FAVORITE:
+                        // TODO: extend beyond DND
+                        mChannelToUpdate.setBypassDnd(!mChannelToUpdate.canBypassDnd());
+                        break;
+                    case ACTION_MUTE:
+                        if (mChannelToUpdate.getImportance() == IMPORTANCE_UNSPECIFIED
+                                || mChannelToUpdate.getImportance() >= IMPORTANCE_DEFAULT) {
+                            mChannelToUpdate.setImportance(IMPORTANCE_LOW);
+                        } else {
+                            mChannelToUpdate.setImportance(Math.max(
+                                    mChannelToUpdate.getOriginalImportance(), IMPORTANCE_DEFAULT));
+                        }
+                        break;
+                    case ACTION_DEMOTE:
+                        // TODO: when demotion status field exists on notificationchannel
+                        break;
+
+                }
+
+                if (mAction != ACTION_HOME && mAction != ACTION_SNOOZE) {
+                    mINotificationManager.updateNotificationChannelForPackage(
+                            mAppPkg, mAppUid, mChannelToUpdate);
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "Unable to update notification channel", e);
+            }
+        }
+    }
+
+    @Retention(SOURCE)
+    @IntDef({BEHAVIOR_ALERTING, BEHAVIOR_SILENT, BEHAVIOR_BUBBLE})
+    private @interface AlertingBehavior {}
+    private static final int BEHAVIOR_ALERTING = 0;
+    private static final int BEHAVIOR_SILENT = 1;
+    private static final int BEHAVIOR_BUBBLE = 2;
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 779a224..6789c81 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -23,7 +23,9 @@
 import android.app.NotificationChannel;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.LauncherApps;
 import android.content.pm.PackageManager;
+import android.content.pm.ShortcutManager;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
@@ -228,6 +230,9 @@
                 initializeAppOpsInfo(row, (AppOpsInfo) gutsView);
             } else if (gutsView instanceof NotificationInfo) {
                 initializeNotificationInfo(row, (NotificationInfo) gutsView);
+            } else if (gutsView instanceof NotificationConversationInfo) {
+                initializeConversationNotificationInfo(
+                        row, (NotificationConversationInfo) gutsView);
             }
             return true;
         } catch (Exception e) {
@@ -339,6 +344,66 @@
     }
 
     /**
+     * Sets up the {@link NotificationConversationInfo} inside the notification row's guts.
+     * @param row view to set up the guts for
+     * @param notificationInfoView view to set up/bind within {@code row}
+     */
+    @VisibleForTesting
+    void initializeConversationNotificationInfo(
+            final ExpandableNotificationRow row,
+            NotificationConversationInfo notificationInfoView) throws Exception {
+        NotificationGuts guts = row.getGuts();
+        StatusBarNotification sbn = row.getEntry().getSbn();
+        String packageName = sbn.getPackageName();
+        // Settings link is only valid for notifications that specify a non-system user
+        NotificationConversationInfo.OnSettingsClickListener onSettingsClick = null;
+        UserHandle userHandle = sbn.getUser();
+        PackageManager pmUser = StatusBar.getPackageManagerForUser(
+                mContext, userHandle.getIdentifier());
+        LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
+        ShortcutManager shortcutManager = mContext.getSystemService(ShortcutManager.class);
+        INotificationManager iNotificationManager = INotificationManager.Stub.asInterface(
+                ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+        final NotificationConversationInfo.OnAppSettingsClickListener onAppSettingsClick =
+                (View v, Intent intent) -> {
+                    mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_APP_NOTE_SETTINGS);
+                    guts.resetFalsingCheck();
+                    mNotificationActivityStarter.startNotificationGutsIntent(intent, sbn.getUid(),
+                            row);
+                };
+
+        final NotificationConversationInfo.OnSnoozeClickListener onSnoozeClickListener =
+                (View v, int hours) -> {
+                    mListContainer.getSwipeActionHelper().snooze(sbn, hours);
+                };
+
+        if (!userHandle.equals(UserHandle.ALL)
+                || mLockscreenUserManager.getCurrentUserId() == UserHandle.USER_SYSTEM) {
+            onSettingsClick = (View v, NotificationChannel channel, int appUid) -> {
+                mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_INFO);
+                guts.resetFalsingCheck();
+                mOnSettingsClickListener.onSettingsClick(sbn.getKey());
+                startAppNotificationSettingsActivity(packageName, appUid, channel, row);
+            };
+        }
+
+        notificationInfoView.bindNotification(
+                shortcutManager,
+                launcherApps,
+                pmUser,
+                iNotificationManager,
+                mVisualStabilityManager,
+                packageName,
+                row.getEntry().getChannel(),
+                row.getEntry(),
+                onSettingsClick,
+                onAppSettingsClick,
+                onSnoozeClickListener,
+                mDeviceProvisionedController.isDeviceProvisioned());
+
+    }
+
+    /**
      * Closes guts or notification menus that might be visible and saves any changes.
      *
      * @param removeLeavebehinds true if leavebehinds (e.g. snooze) should be closed.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index edfd1b4..212cba6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -83,7 +83,6 @@
     private OnMenuEventListener mMenuListener;
     private boolean mDismissRtl;
     private boolean mIsForeground;
-    private final boolean mIsUsingBidirectionalSwipe;
 
     private ValueAnimator mFadeAnimator;
     private boolean mAnimating;
@@ -116,19 +115,11 @@
     private boolean mIsUserTouching;
 
     public NotificationMenuRow(Context context) {
-        //TODO: (b/131242807) not using bidirectional swipe for now
-        this(context, false);
-    }
-
-    // Only needed for testing until we want to turn bidirectional swipe back on
-    @VisibleForTesting
-    NotificationMenuRow(Context context, boolean isUsingBidirectionalSwipe) {
         mContext = context;
         mShouldShowMenu = context.getResources().getBoolean(R.bool.config_showNotificationGear);
         mHandler = new Handler(Looper.getMainLooper());
         mLeftMenuItems = new ArrayList<>();
         mRightMenuItems = new ArrayList<>();
-        mIsUsingBidirectionalSwipe = isUsingBidirectionalSwipe;
     }
 
     @Override
@@ -269,24 +260,18 @@
             mSnoozeItem = createSnoozeItem(mContext);
         }
         mAppOpsItem = createAppOpsItem(mContext);
-        if (mIsUsingBidirectionalSwipe) {
-            mInfoItem = createInfoItem(mContext,
-                    mParent.getEntry().getBucket() == NotificationSectionsManager.BUCKET_SILENT);
+        if (mParent.getEntry().getBucket() == NotificationSectionsManager.BUCKET_PEOPLE) {
+            mInfoItem = createConversationItem(mContext);
         } else {
             mInfoItem = createInfoItem(mContext);
         }
 
-        if (!mIsUsingBidirectionalSwipe) {
-            if (!isForeground && showSnooze) {
-                mRightMenuItems.add(mSnoozeItem);
-            }
-            mRightMenuItems.add(mInfoItem);
-            mRightMenuItems.add(mAppOpsItem);
-            mLeftMenuItems.addAll(mRightMenuItems);
-        } else {
-            ArrayList<MenuItem> menuItems = mDismissRtl ? mLeftMenuItems : mRightMenuItems;
-            menuItems.add(mInfoItem);
+        if (!isForeground && showSnooze) {
+            mRightMenuItems.add(mSnoozeItem);
         }
+        mRightMenuItems.add(mInfoItem);
+        mRightMenuItems.add(mAppOpsItem);
+        mLeftMenuItems.addAll(mRightMenuItems);
 
         populateMenuViews();
         if (resetState) {
@@ -633,12 +618,12 @@
 
     @Override
     public boolean shouldShowGutsOnSnapOpen() {
-        return mIsUsingBidirectionalSwipe;
+        return false;
     }
 
     @Override
     public MenuItem menuItemToExposeOnSnap() {
-        return mIsUsingBidirectionalSwipe ? mInfoItem : null;
+        return null;
     }
 
     @Override
@@ -664,6 +649,16 @@
         return snooze;
     }
 
+    static NotificationMenuItem createConversationItem(Context context) {
+        Resources res = context.getResources();
+        String infoDescription = res.getString(R.string.notification_menu_gear_description);
+        NotificationConversationInfo infoContent =
+                (NotificationConversationInfo) LayoutInflater.from(context).inflate(
+                        R.layout.notification_conversation_info, null, false);
+        return new NotificationMenuItem(context, infoDescription, infoContent,
+                R.drawable.ic_settings);
+    }
+
     static NotificationMenuItem createInfoItem(Context context) {
         Resources res = context.getResources();
         String infoDescription = res.getString(R.string.notification_menu_gear_description);
@@ -673,17 +668,6 @@
                 R.drawable.ic_settings);
     }
 
-    static NotificationMenuItem createInfoItem(Context context, boolean isCurrentlySilent) {
-        Resources res = context.getResources();
-        String infoDescription = res.getString(R.string.notification_menu_gear_description);
-        NotificationInfo infoContent = (NotificationInfo) LayoutInflater.from(context).inflate(
-                R.layout.notification_info, null, false);
-        int iconResId = isCurrentlySilent
-                ? R.drawable.ic_notifications_silence
-                : R.drawable.ic_notifications_alert;
-        return new NotificationMenuItem(context, infoDescription, infoContent, iconResId);
-    }
-
     static MenuItem createAppOpsItem(Context context) {
         AppOpsInfo appOpsContent = (AppOpsInfo) LayoutInflater.from(context).inflate(
                 R.layout.app_ops_info, null, false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 823dd5a..dc2d99c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -6207,6 +6207,11 @@
         }
 
         @Override
+        public void onSnooze(StatusBarNotification sbn, int hours) {
+            mStatusBar.setNotificationSnoozed(sbn, hours);
+        }
+
+        @Override
         public boolean shouldDismissQuickly() {
             return NotificationStackScrollLayout.this.isExpanded() && mAmbientState.isFullyAwake();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index 4845ea1..6c0655e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -282,6 +282,11 @@
         mCallback.onSnooze(sbn, snoozeOption);
     }
 
+    @Override
+    public void snooze(StatusBarNotification sbn, int hours) {
+        mCallback.onSnooze(sbn, hours);
+    }
+
     @VisibleForTesting
     protected void handleMenuCoveredOrDismissed() {
         View exposedMenuView = getExposedMenuView();
@@ -447,6 +452,8 @@
 
         void onSnooze(StatusBarNotification sbn, SnoozeOption snoozeOption);
 
+        void onSnooze(StatusBarNotification sbn, int hours);
+
         void onDismiss();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index a6842ba..4f56f56 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -157,7 +157,6 @@
     private int mNavigationIconHints = 0;
     private @TransitionMode int mNavigationBarMode;
     private AccessibilityManager mAccessibilityManager;
-    private MagnificationContentObserver mMagnificationObserver;
     private ContentResolver mContentResolver;
     private boolean mAssistantAvailable;
 
@@ -303,11 +302,6 @@
         mWindowManager = getContext().getSystemService(WindowManager.class);
         mAccessibilityManager = getContext().getSystemService(AccessibilityManager.class);
         mContentResolver = getContext().getContentResolver();
-        mMagnificationObserver = new MagnificationContentObserver(
-                getContext().getMainThreadHandler());
-        mContentResolver.registerContentObserver(Settings.Secure.getUriFor(
-                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED), false,
-                mMagnificationObserver, UserHandle.USER_ALL);
         mContentResolver.registerContentObserver(
                 Settings.Secure.getUriFor(Settings.Secure.ASSISTANT),
                 false /* notifyForDescendants */, mAssistContentObserver, UserHandle.USER_ALL);
@@ -329,7 +323,6 @@
         super.onDestroy();
         mNavigationModeController.removeListener(this);
         mAccessibilityManagerWrapper.removeCallback(mAccessibilityListener);
-        mContentResolver.unregisterContentObserver(mMagnificationObserver);
         mContentResolver.unregisterContentObserver(mAssistContentObserver);
     }
 
@@ -969,28 +962,18 @@
      * @param outFeedbackEnabled if non-null, sets it to true if accessibility feedback is enabled.
      */
     public int getA11yButtonState(@Nullable boolean[] outFeedbackEnabled) {
-        int requestingServices = 0;
-        try {
-            if (Settings.Secure.getIntForUser(mContentResolver,
-                    Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
-                    UserHandle.USER_CURRENT) == 1) {
-                requestingServices++;
-            }
-        } catch (Settings.SettingNotFoundException e) {
-        }
-
         boolean feedbackEnabled = false;
         // AccessibilityManagerService resolves services for the current user since the local
         // AccessibilityManager is created from a Context with the INTERACT_ACROSS_USERS permission
         final List<AccessibilityServiceInfo> services =
                 mAccessibilityManager.getEnabledAccessibilityServiceList(
                         AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
+        final List<String> a11yButtonTargets =
+                mAccessibilityManager.getAccessibilityShortcutTargets(
+                        AccessibilityManager.ACCESSIBILITY_BUTTON);
+        final int requestingServices = a11yButtonTargets.size();
         for (int i = services.size() - 1; i >= 0; --i) {
             AccessibilityServiceInfo info = services.get(i);
-            if ((info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0) {
-                requestingServices++;
-            }
-
             if (info.feedbackType != 0 && info.feedbackType !=
                     AccessibilityServiceInfo.FEEDBACK_GENERIC) {
                 feedbackEnabled = true;
@@ -1114,18 +1097,6 @@
     private final AccessibilityServicesStateChangeListener mAccessibilityListener =
             this::updateAccessibilityServicesState;
 
-    private class MagnificationContentObserver extends ContentObserver {
-
-        public MagnificationContentObserver(Handler handler) {
-            super(handler);
-        }
-
-        @Override
-        public void onChange(boolean selfChange) {
-            NavigationBarFragment.this.updateAccessibilityServicesState(mAccessibilityManager);
-        }
-    }
-
     private final Consumer<Integer> mRotationWatcher = rotation -> {
         if (mNavigationBarView != null
                 && mNavigationBarView.needsReorient(rotation)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
index e11fc1b..8c947ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -116,7 +116,13 @@
      */
     private void onEntryRemovedInternal(NotificationEntry removed,
             final StatusBarNotification sbn) {
-        String groupKey = getGroupKey(sbn);
+        onEntryRemovedInternal(removed, sbn.getGroupKey(), sbn.isGroup(),
+                sbn.getNotification().isGroupSummary());
+    }
+
+    private void onEntryRemovedInternal(NotificationEntry removed, String notifGroupKey, boolean
+            isGroup, boolean isGroupSummary) {
+        String groupKey = getGroupKey(removed.getKey(), notifGroupKey);
         final NotificationGroup group = mGroupMap.get(groupKey);
         if (group == null) {
             // When an app posts 2 different notifications as summary of the same group, then a
@@ -125,7 +131,7 @@
             // the close future. See b/23676310 for reference.
             return;
         }
-        if (isGroupChild(sbn)) {
+        if (isGroupChild(removed.getKey(), isGroup, isGroupSummary)) {
             group.children.remove(removed.getKey());
         } else {
             group.summary = null;
@@ -229,7 +235,7 @@
     private int getNumberOfIsolatedChildren(String groupKey) {
         int count = 0;
         for (StatusBarNotification sbn : mIsolatedEntries.values()) {
-            if (sbn.getGroupKey().equals(groupKey) && isIsolated(sbn)) {
+            if (sbn.getGroupKey().equals(groupKey) && isIsolated(sbn.getKey())) {
                 count++;
             }
         }
@@ -238,31 +244,47 @@
 
     private NotificationEntry getIsolatedChild(String groupKey) {
         for (StatusBarNotification sbn : mIsolatedEntries.values()) {
-            if (sbn.getGroupKey().equals(groupKey) && isIsolated(sbn)) {
+            if (sbn.getGroupKey().equals(groupKey) && isIsolated(sbn.getKey())) {
                 return mGroupMap.get(sbn.getKey()).summary;
             }
         }
         return null;
     }
 
-    public void onEntryUpdated(NotificationEntry entry,
-            StatusBarNotification oldNotification) {
-        String oldKey = oldNotification.getGroupKey();
-        String newKey = entry.getSbn().getGroupKey();
-        boolean groupKeysChanged = !oldKey.equals(newKey);
-        boolean wasGroupChild = isGroupChild(oldNotification);
+    /**
+     * Update an entry's group information
+     * @param entry notification entry to update
+     * @param oldNotification previous notification info before this update
+     */
+    public void onEntryUpdated(NotificationEntry entry, StatusBarNotification oldNotification) {
+        onEntryUpdated(entry, oldNotification.getGroupKey(), oldNotification.isGroup(),
+                oldNotification.getNotification().isGroupSummary());
+    }
+
+    /**
+     * Updates an entry's group information
+     * @param entry notification entry to update
+     * @param oldGroupKey the notification's previous group key before this update
+     * @param oldIsGroup whether this notification was a group before this update
+     * @param oldIsGroupSummary whether this notification was a group summary before this update
+     */
+    public void onEntryUpdated(NotificationEntry entry, String oldGroupKey, boolean oldIsGroup,
+            boolean oldIsGroupSummary) {
+        String newGroupKey = entry.getSbn().getGroupKey();
+        boolean groupKeysChanged = !oldGroupKey.equals(newGroupKey);
+        boolean wasGroupChild = isGroupChild(entry.getKey(), oldIsGroup, oldIsGroupSummary);
         boolean isGroupChild = isGroupChild(entry.getSbn());
         mIsUpdatingUnchangedGroup = !groupKeysChanged && wasGroupChild == isGroupChild;
-        if (mGroupMap.get(getGroupKey(oldNotification)) != null) {
-            onEntryRemovedInternal(entry, oldNotification);
+        if (mGroupMap.get(getGroupKey(entry.getKey(), oldGroupKey)) != null) {
+            onEntryRemovedInternal(entry, oldGroupKey, oldIsGroup, oldIsGroupSummary);
         }
         onEntryAdded(entry);
         mIsUpdatingUnchangedGroup = false;
-        if (isIsolated(entry.getSbn())) {
+        if (isIsolated(entry.getSbn().getKey())) {
             mIsolatedEntries.put(entry.getKey(), entry.getSbn());
             if (groupKeysChanged) {
-                updateSuppression(mGroupMap.get(oldKey));
-                updateSuppression(mGroupMap.get(newKey));
+                updateSuppression(mGroupMap.get(oldGroupKey));
+                updateSuppression(mGroupMap.get(newGroupKey));
             }
         } else if (!wasGroupChild && isGroupChild) {
             onEntryBecomingChild(entry);
@@ -418,10 +440,14 @@
      * @return the key of the notification
      */
     public String getGroupKey(StatusBarNotification sbn) {
-        if (isIsolated(sbn)) {
-            return sbn.getKey();
+        return getGroupKey(sbn.getKey(), sbn.getGroupKey());
+    }
+
+    private String getGroupKey(String key, String groupKey) {
+        if (isIsolated(key)) {
+            return key;
         }
-        return sbn.getGroupKey();
+        return groupKey;
     }
 
     /** @return group expansion state after toggling. */
@@ -434,8 +460,8 @@
         return group.expanded;
     }
 
-    private boolean isIsolated(StatusBarNotification sbn) {
-        return mIsolatedEntries.containsKey(sbn.getKey());
+    private boolean isIsolated(String sbnKey) {
+        return mIsolatedEntries.containsKey(sbnKey);
     }
 
     /**
@@ -445,7 +471,7 @@
      * @return true if it is visually a group summary
      */
     public boolean isGroupSummary(StatusBarNotification sbn) {
-        if (isIsolated(sbn)) {
+        if (isIsolated(sbn.getKey())) {
             return true;
         }
         return sbn.getNotification().isGroupSummary();
@@ -458,10 +484,14 @@
      * @return true if it is visually a group child
      */
     public boolean isGroupChild(StatusBarNotification sbn) {
-        if (isIsolated(sbn)) {
+        return isGroupChild(sbn.getKey(), sbn.isGroup(), sbn.getNotification().isGroupSummary());
+    }
+
+    private boolean isGroupChild(String key, boolean isGroup, boolean isGroupSummary) {
+        if (isIsolated(key)) {
             return false;
         }
-        return sbn.isGroup() && !sbn.getNotification().isGroupSummary();
+        return isGroup && !isGroupSummary;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index ba70cf4..dc9cf77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -4026,6 +4026,11 @@
         }
     }
 
+    public void setNotificationSnoozed(StatusBarNotification sbn, int hoursToSnooze) {
+        mNotificationListener.snoozeNotification(sbn.getKey(),
+                hoursToSnooze * 60 * 60 * 1000);
+    }
+
     @Override
     public void toggleSplitScreen() {
         toggleSplitScreenMode(-1 /* metricsDockAction */, -1 /* metricsUndockAction */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index 5916180..f05b585 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -15,6 +15,9 @@
  */
 package com.android.systemui.statusbar.policy;
 
+import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
+import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
+
 import android.content.Context;
 import android.content.Intent;
 import android.database.ContentObserver;
@@ -704,7 +707,11 @@
             }
             mServiceState = state;
             if (mServiceState != null) {
-                updateDataNetType(mServiceState.getDataNetworkType());
+                NetworkRegistrationInfo regInfo = mServiceState.getNetworkRegistrationInfo(
+                        DOMAIN_PS, TRANSPORT_TYPE_WWAN);
+                if (regInfo != null) {
+                    updateDataNetType(regInfo.getAccessNetworkTechnology());
+                }
             }
             updateTelephony();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/util/LifecycleActivity.kt b/packages/SystemUI/src/com/android/systemui/util/LifecycleActivity.kt
new file mode 100644
index 0000000..e4b7a20
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/LifecycleActivity.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util
+
+import android.app.Activity
+import android.os.Bundle
+import android.os.PersistableBundle
+import androidx.lifecycle.LifecycleOwner
+import com.android.settingslib.core.lifecycle.Lifecycle
+
+open class LifecycleActivity : Activity(), LifecycleOwner {
+
+    private val lifecycle = Lifecycle(this)
+
+    override fun getLifecycle() = lifecycle
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        lifecycle.onAttach(this)
+        lifecycle.onCreate(savedInstanceState)
+        lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_CREATE)
+        super.onCreate(savedInstanceState)
+    }
+
+    override fun onCreate(
+        savedInstanceState: Bundle?,
+        persistentState: PersistableBundle?
+    ) {
+        lifecycle.onAttach(this)
+        lifecycle.onCreate(savedInstanceState)
+        lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_CREATE)
+        super.onCreate(savedInstanceState, persistentState)
+    }
+
+    override fun onStart() {
+        lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_START)
+        super.onStart()
+    }
+
+    override fun onResume() {
+        lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_RESUME)
+        super.onResume()
+    }
+
+    override fun onPause() {
+        lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_PAUSE)
+        super.onPause()
+    }
+
+    override fun onStop() {
+        lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_STOP)
+        super.onStop()
+    }
+
+    override fun onDestroy() {
+        lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_DESTROY)
+        super.onDestroy()
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
index cca76bd..8a1759d 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
@@ -27,6 +27,9 @@
 import androidx.dynamicanimation.animation.SpringForce
 import com.android.systemui.util.animation.PhysicsAnimator.Companion.getInstance
 import java.util.WeakHashMap
+import kotlin.math.abs
+import kotlin.math.max
+import kotlin.math.min
 
 /**
  * Extension function for all objects which will return a PhysicsAnimator instance for that object.
@@ -35,6 +38,15 @@
 
 private const val TAG = "PhysicsAnimator"
 
+private val UNSET = -Float.MAX_VALUE
+
+/**
+ * [FlingAnimation] multiplies the friction set via [FlingAnimation.setFriction] by 4.2f, which is
+ * where this number comes from. We use it in [PhysicsAnimator.flingThenSpring] to calculate the
+ * minimum velocity for a fling to reach a certain value, given the fling's friction.
+ */
+private const val FLING_FRICTION_SCALAR_MULTIPLIER = 4.2f
+
 typealias EndAction = () -> Unit
 
 /** A map of Property -> AnimationUpdate, which is provided to update listeners on each frame. */
@@ -236,6 +248,71 @@
     }
 
     /**
+     * Flings a property using the given start velocity. If the fling animation reaches the min/max
+     * bounds (from the [flingConfig]) with velocity remaining, it'll overshoot it and spring back.
+     *
+     * If the object is already out of the fling bounds, it will immediately spring back within
+     * bounds.
+     *
+     * This is useful for animating objects that are bounded by constraints such as screen edges,
+     * since otherwise the fling animation would end abruptly upon reaching the min/max bounds.
+     *
+     * @param property The property to animate.
+     * @param startVelocity The velocity, in pixels/second, with which to start the fling. If the
+     * object is already outside the fling bounds, this velocity will be used as the start velocity
+     * of the spring that will spring it back within bounds.
+     * @param flingMustReachMinOrMax If true, the fling animation is guaranteed to reach either its
+     * minimum bound (if [startVelocity] is negative) or maximum bound (if it's positive). The
+     * animator will use startVelocity if it's sufficient, or add more velocity if necessary. This
+     * is useful when fling's deceleration-based physics are preferable to the acceleration-based
+     * forces used by springs - typically, when you're allowing the user to move an object somewhere
+     * on the screen, but it needs to be along an edge.
+     * @param flingConfig The configuration to use for the fling portion of the animation.
+     * @param springConfig The configuration to use for the spring portion of the animation.
+     */
+    @JvmOverloads
+    fun flingThenSpring(
+        property: FloatPropertyCompat<in T>,
+        startVelocity: Float,
+        flingConfig: FlingConfig,
+        springConfig: SpringConfig,
+        flingMustReachMinOrMax: Boolean = false
+    ): PhysicsAnimator<T> {
+        val flingConfigCopy = flingConfig.copy()
+        val springConfigCopy = springConfig.copy()
+        val toAtLeast = if (startVelocity < 0) flingConfig.min else flingConfig.max
+
+        // If the fling needs to reach min/max, calculate the velocity required to do so and use
+        // that if the provided start velocity is not sufficient.
+        if (flingMustReachMinOrMax &&
+                toAtLeast != -Float.MAX_VALUE && toAtLeast != Float.MAX_VALUE) {
+            val distanceToDestination = toAtLeast - property.getValue(target)
+
+            // The minimum velocity required for the fling to end up at the given destination,
+            // taking the provided fling friction value.
+            val velocityToReachDestination = distanceToDestination *
+                    (flingConfig.friction * FLING_FRICTION_SCALAR_MULTIPLIER)
+
+            // Try to use the provided start velocity, but use the required velocity to reach the
+            // destination if the provided velocity is insufficient.
+            val sufficientVelocity =
+                    if (distanceToDestination < 0)
+                        min(velocityToReachDestination, startVelocity)
+                    else
+                        max(velocityToReachDestination, startVelocity)
+
+            flingConfigCopy.startVelocity = sufficientVelocity
+            springConfigCopy.finalPosition = toAtLeast
+        } else {
+            flingConfigCopy.startVelocity = startVelocity
+        }
+
+        flingConfigs[property] = flingConfigCopy
+        springConfigs[property] = springConfigCopy
+        return this
+    }
+
+    /**
      * Adds a listener that will be called whenever any property on the animated object is updated.
      * This will be called on every animation frame, with the current value of the animated object
      * and the new property values.
@@ -246,7 +323,7 @@
     }
 
     /**
-     * Adds a listener that will be called whenever a property's animation ends. This is useful if
+     * Adds a listener that will be called when a property stops animating. This is useful if
      * you care about a specific property ending, or want to use the end value/end velocity from a
      * particular property's animation. If you just want to run an action when all property
      * animations have ended, use [withEndActions].
@@ -311,6 +388,114 @@
                     "your test setup.")
         }
 
+        // Functions that will actually start the animations. These are run after we build and add
+        // the InternalListener, since some animations might update/end immediately and we don't
+        // want to miss those updates.
+        val animationStartActions = ArrayList<() -> Unit>()
+
+        for (animatedProperty in getAnimatedProperties()) {
+            val flingConfig = flingConfigs[animatedProperty]
+            val springConfig = springConfigs[animatedProperty]
+
+            // The property's current value on the object.
+            val currentValue = animatedProperty.getValue(target)
+
+            // Start by checking for a fling configuration. If one is present, we're either flinging
+            // or flinging-then-springing. Either way, we'll want to start the fling first.
+            if (flingConfig != null) {
+                animationStartActions.add {
+                    // When the animation is starting, adjust the min/max bounds to include the
+                    // current value of the property, if necessary. This is required to allow a
+                    // fling to bring an out-of-bounds object back into bounds. For example, if an
+                    // object was dragged halfway off the left side of the screen, but then flung to
+                    // the right, we don't want the animation to end instantly just because the
+                    // object started out of bounds. If the fling is in the direction that would
+                    // take it farther out of bounds, it will end instantly as expected.
+                    flingConfig.apply {
+                        min = min(currentValue, this.min)
+                        max = max(currentValue, this.max)
+                    }
+
+                    // Apply the configuration and start the animation.
+                    getFlingAnimation(animatedProperty)
+                            .also { flingConfig.applyToAnimation(it) }
+                            .start()
+                }
+            }
+
+            // Check for a spring configuration. If one is present, we're either springing, or
+            // flinging-then-springing.
+            if (springConfig != null) {
+
+                // If there is no corresponding fling config, we're only springing.
+                if (flingConfig == null) {
+                    // Apply the configuration and start the animation.
+                    val springAnim = getSpringAnimation(animatedProperty)
+                    springConfig.applyToAnimation(springAnim)
+                    animationStartActions.add(springAnim::start)
+                } else {
+                    // If there's a corresponding fling config, we're flinging-then-springing. Save
+                    // the fling's original bounds so we can spring to them when the fling ends.
+                    val flingMin = flingConfig.min
+                    val flingMax = flingConfig.max
+
+                    // Add an end listener that will start the spring when the fling ends.
+                    endListeners.add(0, object : EndListener<T> {
+                        override fun onAnimationEnd(
+                            target: T,
+                            property: FloatPropertyCompat<in T>,
+                            wasFling: Boolean,
+                            canceled: Boolean,
+                            finalValue: Float,
+                            finalVelocity: Float,
+                            allRelevantPropertyAnimsEnded: Boolean
+                        ) {
+                            // If this isn't the relevant property, it wasn't a fling, or the fling
+                            // was explicitly cancelled, don't spring.
+                            if (property != animatedProperty || !wasFling || canceled) {
+                                return
+                            }
+
+                            val endedWithVelocity = abs(finalVelocity) > 0
+
+                            // If the object was out of bounds when the fling animation started, it
+                            // will immediately end. In that case, we'll spring it back in bounds.
+                            val endedOutOfBounds = finalValue !in flingMin..flingMax
+
+                            // If the fling ended either out of bounds or with remaining velocity,
+                            // it's time to spring.
+                            if (endedWithVelocity || endedOutOfBounds) {
+                                springConfig.startVelocity = finalVelocity
+
+                                // If the spring's final position isn't set, this is a
+                                // flingThenSpring where flingMustReachMinOrMax was false. We'll
+                                // need to set the spring's final position here.
+                                if (springConfig.finalPosition == UNSET) {
+                                    if (endedWithVelocity) {
+                                        // If the fling ended with negative velocity, that means it
+                                        // hit the min bound, so spring to that bound (and vice
+                                        // versa).
+                                        springConfig.finalPosition =
+                                                if (finalVelocity < 0) flingMin else flingMax
+                                    } else if (endedOutOfBounds) {
+                                        // If the fling ended out of bounds, spring it to the
+                                        // nearest bound.
+                                        springConfig.finalPosition =
+                                                if (finalValue < flingMin) flingMin else flingMax
+                                    }
+                                }
+
+                                // Apply the configuration and start the spring animation.
+                                getSpringAnimation(animatedProperty)
+                                        .also { springConfig.applyToAnimation(it) }
+                                        .start()
+                            }
+                        }
+                    })
+                }
+            }
+        }
+
         // Add an internal listener that will dispatch animation events to the provided listeners.
         internalListeners.add(InternalListener(
                 getAnimatedProperties(),
@@ -318,24 +503,10 @@
                 ArrayList(endListeners),
                 ArrayList(endActions)))
 
-        for ((property, config) in flingConfigs) {
-            val currentValue = property.getValue(target)
-
-            // If the fling is already out of bounds, don't start it.
-            if (currentValue <= config.min || currentValue >= config.max) {
-                continue
-            }
-
-            val flingAnim = getFlingAnimation(property)
-            config.applyToAnimation(flingAnim)
-            flingAnim.start()
-        }
-
-        for ((property, config) in springConfigs) {
-            val springAnim = getSpringAnimation(property)
-            config.applyToAnimation(springAnim)
-            springAnim.start()
-        }
+        // Actually start the DynamicAnimations. This is delayed until after the InternalListener is
+        // constructed and added so that we don't miss the end listener firing for any animations
+        // that immediately end.
+        animationStartActions.forEach { it.invoke() }
 
         clearAnimator()
     }
@@ -381,7 +552,10 @@
         }
         anim.addEndListener { _, canceled, value, velocity ->
             internalListeners.removeAll {
-                it.onInternalAnimationEnd(property, canceled, value, velocity) } }
+                it.onInternalAnimationEnd(
+                        property, canceled, value, velocity, anim is FlingAnimation)
+            }
+        }
         return anim
     }
 
@@ -434,7 +608,8 @@
             property: FloatPropertyCompat<in T>,
             canceled: Boolean,
             finalValue: Float,
-            finalVelocity: Float
+            finalVelocity: Float,
+            isFling: Boolean
         ): Boolean {
 
             // If this property animation isn't relevant to this listener, ignore it.
@@ -461,7 +636,15 @@
 
             val allEnded = !arePropertiesAnimating(properties)
             endListeners.forEach {
-                it.onAnimationEnd(target, property, canceled, finalValue, finalVelocity, allEnded) }
+                it.onAnimationEnd(
+                        target, property, isFling, canceled, finalValue, finalVelocity,
+                        allEnded)
+
+                // Check that the end listener didn't restart this property's animation.
+                if (isPropertyAnimating(property)) {
+                    return false
+                }
+            }
 
             // If all of the animations that this listener cares about have ended, run the end
             // actions unless the animation was canceled.
@@ -495,7 +678,8 @@
 
     /** Returns whether the given property is animating.  */
     fun isPropertyAnimating(property: FloatPropertyCompat<in T>): Boolean {
-        return springAnimations[property]?.isRunning ?: false
+        return springAnimations[property]?.isRunning ?: false ||
+                flingAnimations[property]?.isRunning ?: false
     }
 
     /** Returns whether any of the given properties are animating.  */
@@ -523,15 +707,15 @@
     data class SpringConfig internal constructor(
         internal var stiffness: Float,
         internal var dampingRatio: Float,
-        internal var startVel: Float = 0f,
-        internal var finalPosition: Float = -Float.MAX_VALUE
+        internal var startVelocity: Float = 0f,
+        internal var finalPosition: Float = UNSET
     ) {
 
         constructor() :
                 this(defaultSpring.stiffness, defaultSpring.dampingRatio)
 
         constructor(stiffness: Float, dampingRatio: Float) :
-                this(stiffness = stiffness, dampingRatio = dampingRatio, startVel = 0f)
+                this(stiffness = stiffness, dampingRatio = dampingRatio, startVelocity = 0f)
 
         /** Apply these configuration settings to the given SpringAnimation. */
         internal fun applyToAnimation(anim: SpringAnimation) {
@@ -542,7 +726,7 @@
                 finalPosition = this@SpringConfig.finalPosition
             }
 
-            if (startVel != 0f) anim.setStartVelocity(startVel)
+            if (startVelocity != 0f) anim.setStartVelocity(startVelocity)
         }
     }
 
@@ -555,7 +739,7 @@
         internal var friction: Float,
         internal var min: Float,
         internal var max: Float,
-        internal var startVel: Float
+        internal var startVelocity: Float
     ) {
 
         constructor() : this(defaultFling.friction)
@@ -564,7 +748,7 @@
                 this(friction, defaultFling.min, defaultFling.max)
 
         constructor(friction: Float, min: Float, max: Float) :
-                this(friction, min, max, startVel = 0f)
+                this(friction, min, max, startVelocity = 0f)
 
         /** Apply these configuration settings to the given FlingAnimation. */
         internal fun applyToAnimation(anim: FlingAnimation) {
@@ -572,7 +756,7 @@
                 friction = this@FlingConfig.friction
                 setMinValue(min)
                 setMaxValue(max)
-                setStartVelocity(startVel)
+                setStartVelocity(startVelocity)
             }
         }
     }
@@ -625,6 +809,10 @@
          *
          * @param target The animated object itself.
          * @param property The property whose animation has just ended.
+         * @param wasFling Whether this property ended after a fling animation (as opposed to a
+         * spring animation). If this property was animated via [flingThenSpring], this will be true
+         * if the fling animation did not reach the min/max bounds, decelerating to a stop
+         * naturally. It will be false if it hit the bounds and was sprung back.
          * @param canceled Whether the animation was explicitly canceled before it naturally ended.
          * @param finalValue The final value of the animated property.
          * @param finalVelocity The final velocity (in pixels per second) of the ended animation.
@@ -662,6 +850,7 @@
         fun onAnimationEnd(
             target: T,
             property: FloatPropertyCompat<in T>,
+            wasFling: Boolean,
             canceled: Boolean,
             finalValue: Float,
             finalVelocity: Float,
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt
index e86970c..965decd 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt
@@ -19,6 +19,7 @@
 import android.os.Looper
 import android.util.ArrayMap
 import androidx.dynamicanimation.animation.FloatPropertyCompat
+import com.android.systemui.util.animation.PhysicsAnimatorTestUtils.prepareForTest
 import java.util.ArrayDeque
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.TimeUnit
@@ -119,6 +120,7 @@
                 override fun onAnimationEnd(
                     target: T,
                     property: FloatPropertyCompat<in T>,
+                    wasFling: Boolean,
                     canceled: Boolean,
                     finalValue: Float,
                     finalVelocity: Float,
@@ -389,8 +391,6 @@
             val unblockLatch = CountDownLatch(if (startBlocksUntilAnimationsEnd) 2 else 1)
 
             animationThreadHandler.post {
-                val animatedProperties = animator.getAnimatedProperties()
-
                 // Add an update listener that dispatches to any test update listeners added by
                 // tests.
                 animator.addUpdateListener(object : PhysicsAnimator.UpdateListener<T> {
@@ -398,6 +398,10 @@
                         target: T,
                         values: ArrayMap<FloatPropertyCompat<in T>, PhysicsAnimator.AnimationUpdate>
                     ) {
+                        values.forEach { (property, value) ->
+                            allUpdates.getOrPut(property, { ArrayList() }).add(value)
+                        }
+
                         for (listener in testUpdateListeners) {
                             listener.onAnimationUpdateForProperty(target, values)
                         }
@@ -410,6 +414,7 @@
                     override fun onAnimationEnd(
                         target: T,
                         property: FloatPropertyCompat<in T>,
+                        wasFling: Boolean,
                         canceled: Boolean,
                         finalValue: Float,
                         finalVelocity: Float,
@@ -417,7 +422,7 @@
                     ) {
                         for (listener in testEndListeners) {
                             listener.onAnimationEnd(
-                                    target, property, canceled, finalValue, finalVelocity,
+                                    target, property, wasFling, canceled, finalValue, finalVelocity,
                                     allRelevantPropertyAnimsEnded)
                         }
 
@@ -432,31 +437,6 @@
                     }
                 })
 
-                val updateListeners = ArrayList<PhysicsAnimator.UpdateListener<T>>().also {
-                    it.add(object : PhysicsAnimator.UpdateListener<T> {
-                        override fun onAnimationUpdateForProperty(
-                            target: T,
-                            values: ArrayMap<FloatPropertyCompat<in T>,
-                                             PhysicsAnimator.AnimationUpdate>
-                        ) {
-                            values.forEach { (property, value) ->
-                                allUpdates.getOrPut(property, { ArrayList() }).add(value)
-                            }
-                        }
-                    })
-                }
-
-                /**
-                 * Add an internal listener at the head of the list that captures update values
-                 * directly from DynamicAnimation. We use this to build a list of all updates so we
-                 * can verify that InternalListener dispatches to the real listeners properly.
-                 */
-                animator.internalListeners.add(0, animator.InternalListener(
-                        animatedProperties,
-                        updateListeners,
-                        ArrayList(),
-                        ArrayList()))
-
                 animator.startInternal()
                 unblockLatch.countDown()
             }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
new file mode 100644
index 0000000..7c8c7c8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.controller
+
+import android.content.ComponentName
+import android.content.Context
+import android.os.Binder
+import android.service.controls.Control
+import android.service.controls.DeviceTypes
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import dagger.Lazy
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsBindingControllerTest : SysuiTestCase() {
+
+    companion object {
+        fun <T> any(): T = Mockito.any<T>()
+        private val TEST_COMPONENT_NAME_1 = ComponentName("TEST_PKG", "TEST_CLS_1")
+        private val TEST_COMPONENT_NAME_2 = ComponentName("TEST_PKG", "TEST_CLS_2")
+        private val TEST_COMPONENT_NAME_3 = ComponentName("TEST_PKG", "TEST_CLS_3")
+    }
+
+    @Mock
+    private lateinit var mockControlsController: ControlsController
+
+    private val executor = FakeExecutor(FakeSystemClock())
+    private lateinit var controller: ControlsBindingController
+    private val providers = TestableControlsBindingControllerImpl.providers
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        controller = TestableControlsBindingControllerImpl(
+                mContext, executor, Lazy { mockControlsController })
+    }
+
+    @After
+    fun tearDown() {
+        executor.advanceClockToLast()
+        executor.runAllReady()
+        providers.clear()
+    }
+
+    @Test
+    fun testBindAndLoad() {
+        val callback: (List<Control>) -> Unit = {}
+        controller.bindAndLoad(TEST_COMPONENT_NAME_1, callback)
+
+        assertEquals(1, providers.size)
+        val provider = providers.first()
+        verify(provider).maybeBindAndLoad(callback)
+    }
+
+    @Test
+    fun testBindServices() {
+        controller.bindServices(listOf(TEST_COMPONENT_NAME_1, TEST_COMPONENT_NAME_2))
+        executor.runAllReady()
+
+        assertEquals(2, providers.size)
+        assertEquals(setOf(TEST_COMPONENT_NAME_1, TEST_COMPONENT_NAME_2),
+                providers.map { it.componentName }.toSet())
+        providers.forEach {
+            verify(it).bindPermanently()
+        }
+    }
+
+    @Test
+    fun testSubscribe() {
+        val controlInfo1 = ControlInfo(TEST_COMPONENT_NAME_1, "id_1", "", DeviceTypes.TYPE_UNKNOWN)
+        val controlInfo2 = ControlInfo(TEST_COMPONENT_NAME_2, "id_2", "", DeviceTypes.TYPE_UNKNOWN)
+        controller.bindServices(listOf(TEST_COMPONENT_NAME_3))
+
+        controller.subscribe(listOf(controlInfo1, controlInfo2))
+
+        executor.runAllReady()
+
+        assertEquals(3, providers.size)
+        val provider1 = providers.first { it.componentName == TEST_COMPONENT_NAME_1 }
+        val provider2 = providers.first { it.componentName == TEST_COMPONENT_NAME_2 }
+        val provider3 = providers.first { it.componentName == TEST_COMPONENT_NAME_3 }
+
+        verify(provider1).maybeBindAndSubscribe(listOf(controlInfo1.controlId))
+        verify(provider2).maybeBindAndSubscribe(listOf(controlInfo2.controlId))
+        verify(provider3, never()).maybeBindAndSubscribe(any())
+        verify(provider3).unbindService() // Not needed services will be unbound
+    }
+
+    @Test
+    fun testUnsubscribe_notRefreshing() {
+        controller.bindServices(listOf(TEST_COMPONENT_NAME_1, TEST_COMPONENT_NAME_2))
+        controller.unsubscribe()
+
+        executor.runAllReady()
+
+        providers.forEach {
+            verify(it, never()).unsubscribe()
+        }
+    }
+
+    @Test
+    fun testUnsubscribe_refreshing() {
+        val controlInfo1 = ControlInfo(TEST_COMPONENT_NAME_1, "id_1", "", DeviceTypes.TYPE_UNKNOWN)
+        val controlInfo2 = ControlInfo(TEST_COMPONENT_NAME_2, "id_2", "", DeviceTypes.TYPE_UNKNOWN)
+
+        controller.subscribe(listOf(controlInfo1, controlInfo2))
+
+        controller.unsubscribe()
+
+        executor.runAllReady()
+
+        providers.forEach {
+            verify(it).unsubscribe()
+        }
+    }
+}
+
+class TestableControlsBindingControllerImpl(
+    context: Context,
+    executor: DelayableExecutor,
+    lazyController: Lazy<ControlsController>
+) : ControlsBindingControllerImpl(context, executor, lazyController) {
+
+    companion object {
+        val providers = mutableSetOf<ControlsProviderLifecycleManager>()
+    }
+
+    override fun createProviderManager(component: ComponentName):
+            ControlsProviderLifecycleManager {
+        val provider = mock(ControlsProviderLifecycleManager::class.java)
+        val token = Binder()
+        `when`(provider.componentName).thenReturn(component)
+        `when`(provider.token).thenReturn(token)
+        providers.add(provider)
+        return provider
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
new file mode 100644
index 0000000..a19c299
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.controller
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.provider.Settings
+import android.service.controls.Control
+import android.service.controls.DeviceTypes
+import android.service.controls.actions.ControlAction
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.DumpController
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.ControlStatus
+import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import java.util.Optional
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsControllerImplTest : SysuiTestCase() {
+
+    @Mock
+    private lateinit var uiController: ControlsUiController
+    @Mock
+    private lateinit var bindingController: ControlsBindingController
+    @Mock
+    private lateinit var dumpController: DumpController
+    @Mock
+    private lateinit var pendingIntent: PendingIntent
+    @Mock
+    private lateinit var persistenceWrapper: ControlsFavoritePersistenceWrapper
+
+    @Captor
+    private lateinit var controlInfoListCaptor: ArgumentCaptor<List<ControlInfo>>
+    @Captor
+    private lateinit var controlLoadCallbackCaptor: ArgumentCaptor<(List<Control>) -> Unit>
+
+    private lateinit var delayableExecutor: FakeExecutor
+    private lateinit var controller: ControlsController
+
+    companion object {
+        fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
+        fun <T : Any> safeEq(value: T): T = eq(value) ?: value
+
+        private val TEST_COMPONENT = ComponentName("test.pkg", "test.class")
+        private const val TEST_CONTROL_ID = "control1"
+        private const val TEST_CONTROL_TITLE = "Test"
+        private const val TEST_DEVICE_TYPE = DeviceTypes.TYPE_AC_HEATER
+        private val TEST_CONTROL_INFO = ControlInfo(
+                TEST_COMPONENT, TEST_CONTROL_ID, TEST_CONTROL_TITLE, TEST_DEVICE_TYPE)
+
+        private val TEST_COMPONENT_2 = ComponentName("test.pkg", "test.class.2")
+        private const val TEST_CONTROL_ID_2 = "control2"
+        private const val TEST_CONTROL_TITLE_2 = "Test 2"
+        private const val TEST_DEVICE_TYPE_2 = DeviceTypes.TYPE_CAMERA
+        private val TEST_CONTROL_INFO_2 = ControlInfo(
+                TEST_COMPONENT_2, TEST_CONTROL_ID_2, TEST_CONTROL_TITLE_2, TEST_DEVICE_TYPE_2)
+    }
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        Settings.Secure.putInt(mContext.contentResolver,
+                ControlsControllerImpl.CONTROLS_AVAILABLE, 1)
+
+        delayableExecutor = FakeExecutor(FakeSystemClock())
+
+        controller = ControlsControllerImpl(
+                mContext,
+                delayableExecutor,
+                uiController,
+                bindingController,
+                Optional.of(persistenceWrapper),
+                dumpController
+        )
+        assertTrue(controller.available)
+    }
+
+    private fun builderFromInfo(controlInfo: ControlInfo): Control.StatelessBuilder {
+        return Control.StatelessBuilder(controlInfo.controlId, pendingIntent)
+                .setDeviceType(controlInfo.deviceType).setTitle(controlInfo.controlTitle)
+    }
+
+    @Test
+    fun testStartWithoutFavorites() {
+        assertTrue(controller.getFavoriteControls().isEmpty())
+    }
+
+    @Test
+    fun testStartWithSavedFavorites() {
+        `when`(persistenceWrapper.readFavorites()).thenReturn(listOf(TEST_CONTROL_INFO))
+        val controller_other = ControlsControllerImpl(
+                mContext,
+                delayableExecutor,
+                uiController,
+                bindingController,
+                Optional.of(persistenceWrapper),
+                dumpController
+        )
+        assertEquals(listOf(TEST_CONTROL_INFO), controller_other.getFavoriteControls())
+    }
+
+    @Test
+    fun testAddFavorite() {
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+
+        val favorites = controller.getFavoriteControls()
+        assertTrue(TEST_CONTROL_INFO in favorites)
+        assertEquals(1, favorites.size)
+    }
+
+    @Test
+    fun testAddMultipleFavorites() {
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO_2, true)
+
+        val favorites = controller.getFavoriteControls()
+        assertTrue(TEST_CONTROL_INFO in favorites)
+        assertTrue(TEST_CONTROL_INFO_2 in favorites)
+        assertEquals(2, favorites.size)
+    }
+
+    @Test
+    fun testAddAndRemoveFavorite() {
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO_2, true)
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO, false)
+
+        val favorites = controller.getFavoriteControls()
+        assertTrue(TEST_CONTROL_INFO !in favorites)
+        assertTrue(TEST_CONTROL_INFO_2 in favorites)
+        assertEquals(1, favorites.size)
+    }
+
+    @Test
+    fun testFavoritesSavedOnAdd() {
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+
+        verify(persistenceWrapper).storeFavorites(listOf(TEST_CONTROL_INFO))
+    }
+
+    @Test
+    fun testFavoritesSavedOnRemove() {
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+        reset(persistenceWrapper)
+
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO, false)
+        verify(persistenceWrapper).storeFavorites(emptyList())
+    }
+
+    @Test
+    fun testFavoritesSavedOnChange() {
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+        val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2)
+        val control = builderFromInfo(newControlInfo).build()
+
+        controller.loadForComponent(TEST_COMPONENT) {}
+
+        reset(persistenceWrapper)
+        verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT),
+                capture(controlLoadCallbackCaptor))
+
+        controlLoadCallbackCaptor.value.invoke(listOf(control))
+
+        verify(persistenceWrapper).storeFavorites(listOf(newControlInfo))
+    }
+
+    @Test
+    fun testFavoritesNotSavedOnRedundantAdd() {
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+
+        reset(persistenceWrapper)
+
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+        verify(persistenceWrapper, never()).storeFavorites(ArgumentMatchers.anyList())
+    }
+
+    @Test
+    fun testFavoritesNotSavedOnNotRemove() {
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO, false)
+        verify(persistenceWrapper, never()).storeFavorites(ArgumentMatchers.anyList())
+    }
+
+    @Test
+    fun testOnActionResponse() {
+        controller.onActionResponse(TEST_COMPONENT, TEST_CONTROL_ID, ControlAction.RESPONSE_OK)
+
+        verify(uiController).onActionResponse(TEST_COMPONENT, TEST_CONTROL_ID,
+                ControlAction.RESPONSE_OK)
+    }
+
+    @Test
+    fun testRefreshStatus() {
+        val list = listOf(Control.StatefulBuilder(TEST_CONTROL_ID, pendingIntent).build())
+        controller.refreshStatus(TEST_COMPONENT, list)
+
+        verify(uiController).onRefreshState(TEST_COMPONENT, list)
+    }
+
+    @Test
+    fun testUnsubscribe() {
+        controller.unsubscribe()
+        verify(bindingController).unsubscribe()
+    }
+
+    @Test
+    fun testSubscribeFavorites() {
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO_2, true)
+
+        controller.subscribeToFavorites()
+
+        verify(bindingController).subscribe(capture(controlInfoListCaptor))
+
+        assertTrue(TEST_CONTROL_INFO in controlInfoListCaptor.value)
+        assertTrue(TEST_CONTROL_INFO_2 in controlInfoListCaptor.value)
+    }
+
+    @Test
+    fun testLoadForComponent_noFavorites() {
+        var loaded = false
+        val control = builderFromInfo(TEST_CONTROL_INFO).build()
+
+        controller.loadForComponent(TEST_COMPONENT) {
+            loaded = true
+            assertEquals(1, it.size)
+            val controlStatus = it[0]
+            assertEquals(ControlStatus(control, false), controlStatus)
+        }
+
+        verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT),
+                capture(controlLoadCallbackCaptor))
+
+        controlLoadCallbackCaptor.value.invoke(listOf(control))
+
+        assertTrue(loaded)
+    }
+
+    @Test
+    fun testLoadForComponent_favorites() {
+        var loaded = false
+        val control = builderFromInfo(TEST_CONTROL_INFO).build()
+        val control2 = builderFromInfo(TEST_CONTROL_INFO_2).build()
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+
+        controller.loadForComponent(TEST_COMPONENT) {
+            loaded = true
+            assertEquals(2, it.size)
+            val controlStatus = it.first { it.control.controlId == TEST_CONTROL_ID }
+            assertEquals(ControlStatus(control, true), controlStatus)
+
+            val controlStatus2 = it.first { it.control.controlId == TEST_CONTROL_ID_2 }
+            assertEquals(ControlStatus(control2, false), controlStatus2)
+        }
+
+        verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT),
+                capture(controlLoadCallbackCaptor))
+
+        controlLoadCallbackCaptor.value.invoke(listOf(control, control2))
+
+        assertTrue(loaded)
+    }
+
+    @Test
+    fun testLoadForComponent_removed() {
+        var loaded = false
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+
+        controller.loadForComponent(TEST_COMPONENT) {
+            loaded = true
+            assertEquals(1, it.size)
+            val controlStatus = it[0]
+            assertEquals(TEST_CONTROL_ID, controlStatus.control.controlId)
+            assertTrue(controlStatus.favorite)
+            assertTrue(controlStatus.removed)
+        }
+
+        verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT),
+                capture(controlLoadCallbackCaptor))
+
+        controlLoadCallbackCaptor.value.invoke(emptyList())
+
+        assertTrue(loaded)
+    }
+
+    @Test
+    fun testFavoriteInformationModifiedOnLoad() {
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+        val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2)
+        val control = builderFromInfo(newControlInfo).build()
+
+        controller.loadForComponent(TEST_COMPONENT) {}
+
+        verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT),
+                capture(controlLoadCallbackCaptor))
+
+        controlLoadCallbackCaptor.value.invoke(listOf(control))
+
+        val favorites = controller.getFavoriteControls()
+        assertEquals(1, favorites.size)
+        assertEquals(newControlInfo, favorites[0])
+    }
+
+    @Test
+    fun testFavoriteInformationModifiedOnRefresh() {
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+        val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2)
+        val control = builderFromInfo(newControlInfo).build()
+
+        controller.refreshStatus(TEST_COMPONENT, listOf(control))
+
+        delayableExecutor.runAllReady()
+
+        val favorites = controller.getFavoriteControls()
+        assertEquals(1, favorites.size)
+        assertEquals(newControlInfo, favorites[0])
+    }
+
+    @Test
+    fun testClearFavorites() {
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+        assertEquals(1, controller.getFavoriteControls().size)
+
+        controller.clearFavorites()
+        assertTrue(controller.getFavoriteControls().isEmpty())
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt
new file mode 100644
index 0000000..c145c1f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.controller
+
+import android.content.ComponentName
+import android.service.controls.DeviceTypes
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.io.File
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsFavoritePersistenceWrapperTest : SysuiTestCase() {
+
+    private lateinit var file: File
+
+    private val executor = FakeExecutor(FakeSystemClock())
+
+    private lateinit var wrapper: ControlsFavoritePersistenceWrapper
+
+    @Before
+    fun setUp() {
+        file = File.createTempFile("controls_favorites", ".temp")
+        wrapper = ControlsFavoritePersistenceWrapper(file, executor)
+    }
+
+    @After
+    fun tearDown() {
+        if (file.exists() ?: false) {
+            file.delete()
+        }
+    }
+
+    @Test
+    fun testSaveAndRestore() {
+        val controlInfo1 = ControlInfo(
+                ComponentName.unflattenFromString("TEST_PKG/.TEST_CLS_1")!!,
+                "id1", "name_1", DeviceTypes.TYPE_UNKNOWN)
+        val controlInfo2 = ControlInfo(
+                ComponentName.unflattenFromString("TEST_PKG/.TEST_CLS_2")!!,
+                "id2", "name_2", DeviceTypes.TYPE_GENERIC_ON_OFF)
+        val list = listOf(controlInfo1, controlInfo2)
+
+        wrapper.storeFavorites(list)
+
+        executor.runAllReady()
+
+        assertEquals(list, wrapper.readFavorites())
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
new file mode 100644
index 0000000..556bb40
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.controller
+
+import android.content.ComponentName
+import android.service.controls.Control
+import android.service.controls.IControlsProvider
+import android.service.controls.IControlsProviderCallback
+import android.service.controls.actions.ControlAction
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsProviderLifecycleManagerTest : SysuiTestCase() {
+
+    @Mock
+    private lateinit var serviceCallback: IControlsProviderCallback.Stub
+    @Mock
+    private lateinit var service: IControlsProvider.Stub
+
+    private val componentName = ComponentName("test.pkg", "test.cls")
+    private lateinit var manager: ControlsProviderLifecycleManager
+    private lateinit var executor: DelayableExecutor
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        mContext.addMockService(componentName, service)
+        executor = FakeExecutor(FakeSystemClock())
+        `when`(service.asBinder()).thenCallRealMethod()
+        `when`(service.queryLocalInterface(ArgumentMatchers.anyString())).thenReturn(service)
+
+        manager = ControlsProviderLifecycleManager(
+                context,
+                executor,
+                serviceCallback,
+                componentName
+        )
+    }
+
+    @After
+    fun tearDown() {
+        manager.unbindService()
+    }
+
+    @Test
+    fun testBindService() {
+        manager.bindPermanently()
+        assertTrue(mContext.isBound(componentName))
+    }
+
+    @Test
+    fun testUnbindService() {
+        manager.bindPermanently()
+        manager.unbindService()
+        assertFalse(mContext.isBound(componentName))
+    }
+
+    @Test
+    fun testMaybeBindAndLoad() {
+        val callback: (List<Control>) -> Unit = {}
+        manager.maybeBindAndLoad(callback)
+
+        verify(service).load()
+
+        assertTrue(mContext.isBound(componentName))
+        assertEquals(callback, manager.lastLoadCallback)
+    }
+
+    @Test
+    fun testMaybeUnbind_bindingAndCallback() {
+        manager.maybeBindAndLoad {}
+
+        manager.maybeUnbindAndRemoveCallback()
+        assertFalse(mContext.isBound(componentName))
+        assertNull(manager.lastLoadCallback)
+    }
+
+    @Test
+    fun testUnsubscribe() {
+        manager.bindPermanently()
+        manager.unsubscribe()
+
+        verify(service).unsubscribe()
+    }
+
+    @Test
+    fun testMaybeBindAndSubscribe() {
+        val list = listOf("TEST_ID")
+        manager.maybeBindAndSubscribe(list)
+
+        assertTrue(mContext.isBound(componentName))
+        verify(service).subscribe(list)
+    }
+
+    @Test
+    fun testMaybeBindAndAction() {
+        val controlId = "TEST_ID"
+        val action = ControlAction.UNKNOWN_ACTION
+        manager.maybeBindAndSendAction(controlId, action)
+
+        assertTrue(mContext.isBound(componentName))
+        verify(service).onAction(controlId, action)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapperTest.kt
new file mode 100644
index 0000000..d6993c0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapperTest.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.controller
+
+import android.os.RemoteException
+import android.service.controls.IControlsProvider
+import android.service.controls.actions.ControlAction
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.any
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsProviderServiceWrapperTest : SysuiTestCase() {
+
+    @Mock
+    private lateinit var service: IControlsProvider
+
+    private val exception = RemoteException()
+
+    private lateinit var wrapper: ControlsProviderServiceWrapper
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        wrapper = ControlsProviderServiceWrapper(service)
+    }
+
+    @Test
+    fun testLoad_happyPath() {
+        val result = wrapper.load()
+
+        assertTrue(result)
+        verify(service).load()
+    }
+
+    @Test
+    fun testLoad_error() {
+        `when`(service.load()).thenThrow(exception)
+        val result = wrapper.load()
+
+        assertFalse(result)
+    }
+
+    @Test
+    fun testSubscribe_happyPath() {
+        val list = listOf("TEST_ID")
+        val result = wrapper.subscribe(list)
+
+        assertTrue(result)
+        verify(service).subscribe(list)
+    }
+
+    @Test
+    fun testSubscribe_error() {
+        `when`(service.subscribe(any())).thenThrow(exception)
+
+        val list = listOf("TEST_ID")
+        val result = wrapper.subscribe(list)
+
+        assertFalse(result)
+    }
+
+    @Test
+    fun testUnsubscribe_happyPath() {
+        val result = wrapper.unsubscribe()
+
+        assertTrue(result)
+        verify(service).unsubscribe()
+    }
+
+    @Test
+    fun testUnsubscribe_error() {
+        `when`(service.unsubscribe()).thenThrow(exception)
+        val result = wrapper.unsubscribe()
+
+        assertFalse(result)
+    }
+
+    @Test
+    fun testOnAction_happyPath() {
+        val id = "TEST_ID"
+        val action = ControlAction.UNKNOWN_ACTION
+
+        val result = wrapper.onAction(id, action)
+
+        assertTrue(result)
+        verify(service).onAction(id, action)
+    }
+
+    @Test
+    fun testOnAction_error() {
+        `when`(service.onAction(any(), any())).thenThrow(exception)
+
+        val id = "TEST_ID"
+        val action = ControlAction.UNKNOWN_ACTION
+
+        val result = wrapper.onAction(id, action)
+
+        assertFalse(result)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
new file mode 100644
index 0000000..f09aab9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.management
+
+import android.content.ComponentName
+import android.content.pm.ServiceInfo
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.settingslib.applications.ServiceListing
+import com.android.settingslib.widget.CandidateInfo
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsListingControllerImplTest : SysuiTestCase() {
+
+    companion object {
+        private const val TEST_LABEL = "TEST_LABEL"
+        private const val TEST_PERMISSION = "permission"
+        fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
+        fun <T> any(): T = Mockito.any<T>()
+    }
+
+    @Mock
+    private lateinit var mockSL: ServiceListing
+    @Mock
+    private lateinit var mockCallback: ControlsListingController.ControlsListingCallback
+    @Mock
+    private lateinit var mockCallbackOther: ControlsListingController.ControlsListingCallback
+    @Mock
+    private lateinit var serviceInfo: ServiceInfo
+    @Mock
+    private lateinit var componentName: ComponentName
+
+    private val executor = FakeExecutor(FakeSystemClock())
+
+    private lateinit var controller: ControlsListingControllerImpl
+
+    private var serviceListingCallbackCaptor =
+            ArgumentCaptor.forClass(ServiceListing.Callback::class.java)
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        `when`(serviceInfo.componentName).thenReturn(componentName)
+
+        controller = ControlsListingControllerImpl(mContext, executor, mockSL)
+        verify(mockSL).addCallback(capture(serviceListingCallbackCaptor))
+    }
+
+    @After
+    fun tearDown() {
+        executor.advanceClockToLast()
+        executor.runAllReady()
+    }
+
+    @Test
+    fun testNoServices_notListening() {
+        assertTrue(controller.getCurrentServices().isEmpty())
+    }
+
+    @Test
+    fun testStartListening_onFirstCallback() {
+        controller.addCallback(mockCallback)
+        executor.runAllReady()
+
+        verify(mockSL).setListening(true)
+    }
+
+    @Test
+    fun testStartListening_onlyOnce() {
+        controller.addCallback(mockCallback)
+        controller.addCallback(mockCallbackOther)
+
+        executor.runAllReady()
+
+        verify(mockSL).setListening(true)
+    }
+
+    @Test
+    fun testStopListening_callbackRemoved() {
+        controller.addCallback(mockCallback)
+
+        executor.runAllReady()
+
+        controller.removeCallback(mockCallback)
+
+        executor.runAllReady()
+
+        verify(mockSL).setListening(false)
+    }
+
+    @Test
+    fun testStopListening_notWhileRemainingCallbacks() {
+        controller.addCallback(mockCallback)
+        controller.addCallback(mockCallbackOther)
+
+        executor.runAllReady()
+
+        controller.removeCallback(mockCallback)
+
+        executor.runAllReady()
+
+        verify(mockSL, never()).setListening(false)
+    }
+
+    @Test
+    fun testReloadOnFirstCallbackAdded() {
+        controller.addCallback(mockCallback)
+        executor.runAllReady()
+
+        verify(mockSL).reload()
+    }
+
+    @Test
+    fun testCallbackCalledWhenAdded() {
+        `when`(mockSL.reload()).then {
+            serviceListingCallbackCaptor.value.onServicesReloaded(emptyList())
+        }
+
+        controller.addCallback(mockCallback)
+        executor.runAllReady()
+        verify(mockCallback).onServicesUpdated(any())
+        reset(mockCallback)
+
+        controller.addCallback(mockCallbackOther)
+        executor.runAllReady()
+        verify(mockCallbackOther).onServicesUpdated(any())
+        verify(mockCallback, never()).onServicesUpdated(any())
+    }
+
+    @Test
+    fun testCallbackGetsList() {
+        val list = listOf(serviceInfo)
+        controller.addCallback(mockCallback)
+        controller.addCallback(mockCallbackOther)
+
+        @Suppress("unchecked_cast")
+        val captor: ArgumentCaptor<List<CandidateInfo>> =
+                ArgumentCaptor.forClass(List::class.java) as ArgumentCaptor<List<CandidateInfo>>
+
+        executor.runAllReady()
+        reset(mockCallback)
+        reset(mockCallbackOther)
+
+        serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+        executor.runAllReady()
+        verify(mockCallback).onServicesUpdated(capture(captor))
+        assertEquals(1, captor.value.size)
+        assertEquals(componentName.flattenToString(), captor.value[0].key)
+
+        verify(mockCallbackOther).onServicesUpdated(capture(captor))
+        assertEquals(1, captor.value.size)
+        assertEquals(componentName.flattenToString(), captor.value[0].key)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index 09cc5ba..fe8d769 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -29,6 +29,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -48,6 +49,7 @@
 
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.DumpController;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.RankingBuilder;
 import com.android.systemui.statusbar.notification.collection.NoManSimulator.NotifEvent;
@@ -103,7 +105,7 @@
         MockitoAnnotations.initMocks(this);
         Assert.sMainLooper = TestableLooper.get(this).getLooper();
 
-        mCollection = new NotifCollection(mStatusBarService);
+        mCollection = new NotifCollection(mStatusBarService, mock(DumpController.class));
         mCollection.attach(mGroupCoalescer);
         mCollection.addCollectionListener(mCollectionListener);
         mCollection.setBuildListener(mBuildListener);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index be06748..e915be3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -16,9 +16,10 @@
 
 package com.android.systemui.statusbar.notification.collection;
 
-import static com.android.systemui.statusbar.notification.collection.ListDumper.dumpList;
+import static com.android.systemui.statusbar.notification.collection.ListDumper.dumpTree;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyList;
@@ -27,6 +28,7 @@
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
@@ -37,6 +39,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.DumpController;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.notification.collection.ShadeListBuilder.OnRenderListListener;
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
@@ -45,7 +48,7 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.SectionsProvider;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
 import com.android.systemui.statusbar.notification.logging.NotifLog;
 import com.android.systemui.util.Assert;
@@ -100,7 +103,7 @@
         MockitoAnnotations.initMocks(this);
         Assert.sMainLooper = TestableLooper.get(this).getLooper();
 
-        mListBuilder = new ShadeListBuilder(mSystemClock, mNotifLog);
+        mListBuilder = new ShadeListBuilder(mSystemClock, mNotifLog, mock(DumpController.class));
         mListBuilder.setOnRenderListListener(mOnRenderListListener);
 
         mListBuilder.attach(mNotifCollection);
@@ -448,6 +451,29 @@
     }
 
     @Test
+    public void testPreRenderNotifsFilteredBreakupGroups() {
+        final String filterTag = "FILTER_ME";
+        // GIVEN a NotifFilter that filters out notifications with a tag
+        NotifFilter filter1 = spy(new NotifFilterWithTag(filterTag));
+        mListBuilder.addPreRenderFilter(filter1);
+
+        // WHEN the pipeline is kicked off on a list of notifs
+        addGroupChildWithTag(0, PACKAGE_2, GROUP_1, filterTag);
+        addGroupChild(1, PACKAGE_2, GROUP_1);
+        addGroupSummary(2, PACKAGE_2, GROUP_1);
+        dispatchBuild();
+
+        // THEN the final list doesn't contain any filtered-out notifs
+        // and groups that are too small are broken up
+        verifyBuiltList(
+                notif(1)
+        );
+
+        // THEN each filtered notif records the filter that did it
+        assertEquals(filter1, mEntrySet.get(0).mExcludingFilter);
+    }
+
+    @Test
     public void testNotifFiltersCanBePreempted() {
         // GIVEN two notif filters
         NotifFilter filter1 = spy(new PackageFilter(PACKAGE_2));
@@ -550,12 +576,17 @@
     }
 
     @Test
-    public void testNotifsAreSectioned() {
-        // GIVEN a filter that removes all PACKAGE_4 notifs and a SectionsProvider that divides
+    public void testNotifSections() {
+        // GIVEN a filter that removes all PACKAGE_4 notifs and sections that divide
         // notifs based on package name
         mListBuilder.addPreGroupFilter(new PackageFilter(PACKAGE_4));
-        final SectionsProvider sectionsProvider = spy(new PackageSectioner());
-        mListBuilder.setSectionsProvider(sectionsProvider);
+        final NotifSection pkg1Section = spy(new PackageSection(PACKAGE_1));
+        final NotifSection pkg2Section = spy(new PackageSection(PACKAGE_2));
+        // NOTE: no package 3 section explicitly added, so notifs with package 3 will get set by
+        // ShadeListBuilder's sDefaultSection which will demote it to the last section
+        final NotifSection pkg4Section = spy(new PackageSection(PACKAGE_4));
+        final NotifSection pkg5Section = spy(new PackageSection(PACKAGE_5));
+        mListBuilder.setSections(Arrays.asList(pkg1Section, pkg2Section, pkg4Section, pkg5Section));
 
         // WHEN we build a list with different packages
         addNotif(0, PACKAGE_4);
@@ -582,19 +613,93 @@
                         child(6)
                 ),
                 notif(8),
-                notif(3),
-                notif(9)
+                notif(9),
+                notif(3)
         );
 
-        // THEN the sections provider is called on all top level elements (but no children and no
-        // entries that were filtered out)
-        verify(sectionsProvider).getSection(mEntrySet.get(1));
-        verify(sectionsProvider).getSection(mEntrySet.get(2));
-        verify(sectionsProvider).getSection(mEntrySet.get(3));
-        verify(sectionsProvider).getSection(mEntrySet.get(7));
-        verify(sectionsProvider).getSection(mEntrySet.get(8));
-        verify(sectionsProvider).getSection(mEntrySet.get(9));
-        verify(sectionsProvider).getSection(mBuiltList.get(3));
+        // THEN the first section (pkg1Section) is called on all top level elements (but
+        // no children and no entries that were filtered out)
+        verify(pkg1Section).isInSection(mEntrySet.get(1));
+        verify(pkg1Section).isInSection(mEntrySet.get(2));
+        verify(pkg1Section).isInSection(mEntrySet.get(3));
+        verify(pkg1Section).isInSection(mEntrySet.get(7));
+        verify(pkg1Section).isInSection(mEntrySet.get(8));
+        verify(pkg1Section).isInSection(mEntrySet.get(9));
+        verify(pkg1Section).isInSection(mBuiltList.get(3));
+
+        verify(pkg1Section, never()).isInSection(mEntrySet.get(0));
+        verify(pkg1Section, never()).isInSection(mEntrySet.get(4));
+        verify(pkg1Section, never()).isInSection(mEntrySet.get(5));
+        verify(pkg1Section, never()).isInSection(mEntrySet.get(6));
+        verify(pkg1Section, never()).isInSection(mEntrySet.get(10));
+
+        // THEN the last section (pkg5Section) is not called on any of the entries that were
+        // filtered or already in a section
+        verify(pkg5Section, never()).isInSection(mEntrySet.get(0));
+        verify(pkg5Section, never()).isInSection(mEntrySet.get(1));
+        verify(pkg5Section, never()).isInSection(mEntrySet.get(2));
+        verify(pkg5Section, never()).isInSection(mEntrySet.get(4));
+        verify(pkg5Section, never()).isInSection(mEntrySet.get(5));
+        verify(pkg5Section, never()).isInSection(mEntrySet.get(6));
+        verify(pkg5Section, never()).isInSection(mEntrySet.get(7));
+        verify(pkg5Section, never()).isInSection(mEntrySet.get(8));
+        verify(pkg5Section, never()).isInSection(mEntrySet.get(10));
+
+        verify(pkg5Section).isInSection(mEntrySet.get(3));
+        verify(pkg5Section).isInSection(mEntrySet.get(9));
+
+        // THEN the correct section is assigned for entries in pkg1Section
+        assertEquals(pkg1Section, mEntrySet.get(2).mNotifSection);
+        assertEquals(0, mEntrySet.get(2).getSection());
+        assertEquals(pkg1Section, mEntrySet.get(7).mNotifSection);
+        assertEquals(0, mEntrySet.get(7).getSection());
+
+        // THEN the correct section is assigned for entries in pkg2Section
+        assertEquals(pkg2Section, mEntrySet.get(1).mNotifSection);
+        assertEquals(1, mEntrySet.get(1).getSection());
+        assertEquals(pkg2Section, mEntrySet.get(8).mNotifSection);
+        assertEquals(1, mEntrySet.get(8).getSection());
+        assertEquals(pkg2Section, mBuiltList.get(3).mNotifSection);
+        assertEquals(1, mBuiltList.get(3).getSection());
+
+        // THEN no section was assigned to entries in pkg4Section (since they were filtered)
+        assertEquals(null, mEntrySet.get(0).mNotifSection);
+        assertEquals(-1, mEntrySet.get(0).getSection());
+        assertEquals(null, mEntrySet.get(10).mNotifSection);
+        assertEquals(-1, mEntrySet.get(10).getSection());
+
+
+        // THEN the correct section is assigned for entries in pkg5Section
+        assertEquals(pkg5Section, mEntrySet.get(9).mNotifSection);
+        assertEquals(3, mEntrySet.get(9).getSection());
+
+        // THEN the children entries are assigned the same section as its parent
+        assertEquals(mBuiltList.get(3).mNotifSection, child(5).entry.mNotifSection);
+        assertEquals(mBuiltList.get(3).getSection(), child(5).entry.getSection());
+        assertEquals(mBuiltList.get(3).mNotifSection, child(6).entry.mNotifSection);
+        assertEquals(mBuiltList.get(3).getSection(), child(6).entry.getSection());
+    }
+
+    @Test
+    public void testNotifUsesDefaultSection() {
+        // GIVEN a Section for Package2
+        final NotifSection pkg2Section = spy(new PackageSection(PACKAGE_2));
+        mListBuilder.setSections(Arrays.asList(pkg2Section));
+
+        // WHEN we build a list with pkg1 and pkg2 packages
+        addNotif(0, PACKAGE_1);
+        addNotif(1, PACKAGE_2);
+        dispatchBuild();
+
+        // THEN the list is sorted according to section
+        verifyBuiltList(
+                notif(1),
+                notif(0)
+        );
+
+        // THEN the entry that didn't have an explicit section gets assigned the DefaultSection
+        assertEquals(1, notif(0).entry.getSection());
+        assertNotNull(notif(0).entry.mNotifSection);
     }
 
     @Test
@@ -629,7 +734,7 @@
         // GIVEN a bunch of registered listeners and pluggables
         NotifFilter preGroupFilter = spy(new PackageFilter(PACKAGE_1));
         NotifPromoter promoter = spy(new IdPromoter(3));
-        PackageSectioner sectioner = spy(new PackageSectioner());
+        NotifSection section = spy(new PackageSection(PACKAGE_1));
         NotifComparator comparator = spy(new HypeComparator(PACKAGE_4));
         NotifFilter preRenderFilter = spy(new PackageFilter(PACKAGE_5));
         mListBuilder.addPreGroupFilter(preGroupFilter);
@@ -637,7 +742,7 @@
         mListBuilder.addPromoter(promoter);
         mListBuilder.addOnBeforeSortListener(mOnBeforeSortListener);
         mListBuilder.setComparators(Collections.singletonList(comparator));
-        mListBuilder.setSectionsProvider(sectioner);
+        mListBuilder.setSections(Arrays.asList(section));
         mListBuilder.addOnBeforeRenderListListener(mOnBeforeRenderListListener);
         mListBuilder.addPreRenderFilter(preRenderFilter);
 
@@ -657,7 +762,7 @@
                 mOnBeforeTransformGroupsListener,
                 promoter,
                 mOnBeforeSortListener,
-                sectioner,
+                section,
                 comparator,
                 preRenderFilter,
                 mOnBeforeRenderListListener,
@@ -670,7 +775,7 @@
         inOrder.verify(promoter, atLeastOnce())
                 .shouldPromoteToTopLevel(any(NotificationEntry.class));
         inOrder.verify(mOnBeforeSortListener).onBeforeSort(anyList());
-        inOrder.verify(sectioner, atLeastOnce()).getSection(any(ListEntry.class));
+        inOrder.verify(section, atLeastOnce()).isInSection(any(ListEntry.class));
         inOrder.verify(comparator, atLeastOnce())
                 .compare(any(ListEntry.class), any(ListEntry.class));
         inOrder.verify(preRenderFilter, atLeastOnce())
@@ -684,12 +789,12 @@
         // GIVEN a variety of pluggables
         NotifFilter packageFilter = new PackageFilter(PACKAGE_1);
         NotifPromoter idPromoter = new IdPromoter(4);
-        SectionsProvider sectionsProvider = new PackageSectioner();
+        NotifSection section = new PackageSection(PACKAGE_1);
         NotifComparator hypeComparator = new HypeComparator(PACKAGE_2);
 
         mListBuilder.addPreGroupFilter(packageFilter);
         mListBuilder.addPromoter(idPromoter);
-        mListBuilder.setSectionsProvider(sectionsProvider);
+        mListBuilder.setSections(Arrays.asList(section));
         mListBuilder.setComparators(Collections.singletonList(hypeComparator));
 
         // GIVEN a set of random notifs
@@ -709,7 +814,7 @@
         verify(mOnRenderListListener).onRenderList(anyList());
 
         clearInvocations(mOnRenderListListener);
-        sectionsProvider.invalidateList();
+        section.invalidateList();
         verify(mOnRenderListListener).onRenderList(anyList());
 
         clearInvocations(mOnRenderListListener);
@@ -982,9 +1087,10 @@
         return builder;
     }
 
-    /** Same behavior as {@link #addNotif(int, String)}. */
-    private NotificationEntryBuilder addGroupChild(int index, String packageId, String groupId) {
+    private NotificationEntryBuilder addGroupChildWithTag(int index, String packageId,
+            String groupId, String tag) {
         final NotificationEntryBuilder builder = new NotificationEntryBuilder()
+                .setTag(tag)
                 .setPkg(packageId)
                 .setId(nextId(packageId))
                 .setRank(nextRank());
@@ -999,6 +1105,11 @@
         return builder;
     }
 
+    /** Same behavior as {@link #addNotif(int, String)}. */
+    private NotificationEntryBuilder addGroupChild(int index, String packageId, String groupId) {
+        return addGroupChildWithTag(index, packageId, groupId, null);
+    }
+
     private int nextId(String packageName) {
         Integer nextId = mNextIdMap.get(packageName);
         if (nextId == null) {
@@ -1081,7 +1192,8 @@
             }
         } catch (AssertionError err) {
             throw new AssertionError(
-                    "List under test failed verification:\n" + dumpList(mBuiltList), err);
+                    "List under test failed verification:\n" + dumpTree(mBuiltList,
+                            true, ""), err);
         }
     }
 
@@ -1166,6 +1278,21 @@
         }
     }
 
+    /** Filters out notifications with a particular tag */
+    private static class NotifFilterWithTag extends NotifFilter {
+        private final String mTag;
+
+        NotifFilterWithTag(String tag) {
+            super("NotifFilterWithTag_" + tag);
+            mTag = tag;
+        }
+
+        @Override
+        public boolean shouldFilterOut(NotificationEntry entry, long now) {
+            return Objects.equals(entry.getSbn().getTag(), mTag);
+        }
+    }
+
     /** Promotes notifs with particular IDs */
     private static class IdPromoter extends NotifPromoter {
         private final List<Integer> mIds;
@@ -1202,25 +1329,18 @@
         }
     }
 
-    /** Sorts notifs into sections based on their package name */
-    private static class PackageSectioner extends SectionsProvider {
+    /** Represents a section for the passed pkg */
+    private static class PackageSection extends NotifSection {
+        private final String mPackage;
 
-        PackageSectioner() {
-            super("PackageSectioner");
+        PackageSection(String pkg) {
+            super("PackageSection_" + pkg);
+            mPackage = pkg;
         }
 
         @Override
-        public int getSection(ListEntry entry) {
-            switch (entry.getRepresentativeEntry().getSbn().getPackageName()) {
-                case PACKAGE_1:
-                    return 1;
-                case PACKAGE_2:
-                    return 2;
-                case PACKAGE_3:
-                    return 3;
-                default:
-                    return 4;
-            }
+        public boolean isInSection(ListEntry entry) {
+            return entry.getRepresentativeEntry().getSbn().getPackageName().equals(mPackage);
         }
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
new file mode 100644
index 0000000..0bf458c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -0,0 +1,816 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.notification.row;
+
+import static android.app.Notification.FLAG_BUBBLE;
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.print.PrintManager.PRINT_SPOOLER_PACKAGE_NAME;
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.INotificationManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
+import android.app.PendingIntent;
+import android.app.Person;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.Slog;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.BubblesTestActivity;
+import com.android.systemui.statusbar.SbnBuilder;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.mockito.stubbing.Answer;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class NotificationConversationInfoTest extends SysuiTestCase {
+    private static final String TEST_PACKAGE_NAME = "test_package";
+    private static final String TEST_SYSTEM_PACKAGE_NAME = PRINT_SPOOLER_PACKAGE_NAME;
+    private static final int TEST_UID = 1;
+    private static final String TEST_CHANNEL = "test_channel";
+    private static final String TEST_CHANNEL_NAME = "TEST CHANNEL NAME";
+    private static final String CONVERSATION_ID = "convo";
+
+    private TestableLooper mTestableLooper;
+    private NotificationConversationInfo mNotificationInfo;
+    private NotificationChannel mNotificationChannel;
+    private NotificationChannel mConversationChannel;
+    private StatusBarNotification mSbn;
+    private NotificationEntry mEntry;
+    private StatusBarNotification mBubbleSbn;
+    private NotificationEntry mBubbleEntry;
+    @Mock
+    private ShortcutInfo mShortcutInfo;
+    private Drawable mImage;
+
+    @Rule
+    public MockitoRule mockito = MockitoJUnit.rule();
+    @Mock
+    private MetricsLogger mMetricsLogger;
+    @Mock
+    private INotificationManager mMockINotificationManager;
+    @Mock
+    private PackageManager mMockPackageManager;
+    @Mock
+    private VisualStabilityManager mVisualStabilityManager;
+    @Mock
+    private BubbleController mBubbleController;
+    @Mock
+    private LauncherApps mLauncherApps;
+    @Mock
+    private ShortcutManager mShortcutManager;
+    @Mock
+    private NotificationGuts mNotificationGuts;
+
+    @Before
+    public void setUp() throws Exception {
+        mTestableLooper = TestableLooper.get(this);
+
+        mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
+        mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
+        mDependency.injectTestDependency(BubbleController.class, mBubbleController);
+        // Inflate the layout
+        final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
+        mNotificationInfo = (NotificationConversationInfo) layoutInflater.inflate(
+                R.layout.notification_conversation_info,
+                null);
+        mNotificationInfo.mShowHomeScreen = true;
+        mNotificationInfo.setGutsParent(mNotificationGuts);
+        doAnswer((Answer<Object>) invocation -> {
+            mNotificationInfo.handleCloseControls(true, false);
+            return null;
+        }).when(mNotificationGuts).closeControls(anyInt(), anyInt(), eq(true), eq(false));
+        // Our view is never attached to a window so the View#post methods in NotificationInfo never
+        // get called. Setting this will skip the post and do the action immediately.
+        mNotificationInfo.mSkipPost = true;
+
+        // PackageManager must return a packageInfo and applicationInfo.
+        final PackageInfo packageInfo = new PackageInfo();
+        packageInfo.packageName = TEST_PACKAGE_NAME;
+        when(mMockPackageManager.getPackageInfo(eq(TEST_PACKAGE_NAME), anyInt()))
+                .thenReturn(packageInfo);
+        final ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.uid = TEST_UID;  // non-zero
+        when(mMockPackageManager.getApplicationInfo(eq(TEST_PACKAGE_NAME), anyInt())).thenReturn(
+                applicationInfo);
+        final PackageInfo systemPackageInfo = new PackageInfo();
+        systemPackageInfo.packageName = TEST_SYSTEM_PACKAGE_NAME;
+        when(mMockPackageManager.getPackageInfo(eq(TEST_SYSTEM_PACKAGE_NAME), anyInt()))
+                .thenReturn(systemPackageInfo);
+        when(mMockPackageManager.getPackageInfo(eq("android"), anyInt()))
+                .thenReturn(packageInfo);
+
+        when(mShortcutInfo.getShortLabel()).thenReturn("Convo name");
+        List<ShortcutInfo> shortcuts = Arrays.asList(mShortcutInfo);
+        when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcuts);
+        mImage = mContext.getDrawable(R.drawable.ic_star);
+        when(mLauncherApps.getShortcutBadgedIconDrawable(eq(mShortcutInfo),
+                anyInt())).thenReturn(mImage);
+
+        mNotificationChannel = new NotificationChannel(
+                TEST_CHANNEL, TEST_CHANNEL_NAME, IMPORTANCE_LOW);
+
+        Notification notification = new Notification.Builder(mContext, mNotificationChannel.getId())
+                .setShortcutId(CONVERSATION_ID)
+                .setStyle(new Notification.MessagingStyle(new Person.Builder().setName("m").build())
+                        .addMessage(new Notification.MessagingStyle.Message(
+                                "hello!", 1000, new Person.Builder().setName("other").build())))
+                .build();
+        mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0,
+                notification, UserHandle.CURRENT, null, 0);
+        mEntry = new NotificationEntryBuilder().setSbn(mSbn).build();
+
+        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0,
+                new Intent(mContext, BubblesTestActivity.class), 0);
+        mBubbleSbn = new SbnBuilder(mSbn).setBubbleMetadata(
+                new Notification.BubbleMetadata.Builder()
+                        .setIntent(bubbleIntent)
+                        .setIcon(Icon.createWithResource(mContext, R.drawable.android)).build())
+                .build();
+        mBubbleEntry = new NotificationEntryBuilder().setSbn(mBubbleSbn).build();
+
+        mConversationChannel = new NotificationChannel(
+                TEST_CHANNEL + " : " + CONVERSATION_ID, TEST_CHANNEL_NAME, IMPORTANCE_LOW);
+        mConversationChannel.setConversationId(TEST_CHANNEL, CONVERSATION_ID);
+        when(mMockINotificationManager.getConversationNotificationChannel(anyString(), anyInt(),
+                anyString(), eq(TEST_CHANNEL), eq(false), eq(CONVERSATION_ID)))
+                .thenReturn(mConversationChannel);
+    }
+
+    @Test
+    public void testBindNotification_SetsTextShortcutName() {
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                null,
+                null,
+                null,
+                true);
+        final TextView textView = mNotificationInfo.findViewById(R.id.name);
+        assertEquals(mShortcutInfo.getShortLabel(), textView.getText().toString());
+        assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
+    }
+
+    @Test
+    public void testBindNotification_SetsShortcutIcon() {
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                null,
+                null,
+                null,
+                true);
+        final ImageView view = mNotificationInfo.findViewById(R.id.conversation_icon);
+        assertEquals(mImage, view.getDrawable());
+    }
+
+    @Test
+    public void testBindNotification_SetsTextApplicationName() {
+        when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name");
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                null,
+                null,
+                null,
+                true);
+        final TextView textView = mNotificationInfo.findViewById(R.id.pkg_name);
+        assertTrue(textView.getText().toString().contains("App Name"));
+        assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
+    }
+
+    @Test
+    public void testBindNotification_SetsTextChannelName() {
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                null,
+                null,
+                null,
+                true);
+        final TextView textView = mNotificationInfo.findViewById(R.id.parent_channel_name);
+        assertTrue(textView.getText().toString().contains(mNotificationChannel.getName()));
+        assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
+    }
+
+    @Test
+    public void testBindNotification_SetsTextGroupName() throws Exception {
+        NotificationChannelGroup group = new NotificationChannelGroup("id", "name");
+        when(mMockINotificationManager.getNotificationChannelGroupForPackage(
+               anyString(), anyString(), anyInt())).thenReturn(group);
+        mNotificationChannel.setGroup(group.getId());
+        mConversationChannel.setGroup(group.getId());
+
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                null,
+                null,
+                null,
+                true);
+        final TextView textView = mNotificationInfo.findViewById(R.id.group_name);
+        assertTrue(textView.getText().toString().contains(group.getName()));
+        assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
+        assertEquals(VISIBLE, textView.getVisibility());
+        assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.group_divider).getVisibility());
+    }
+
+    @Test
+    public void testBindNotification_GroupNameHiddenIfNoGroup() {
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                null,
+                null,
+                null,
+                true);
+        final TextView textView = mNotificationInfo.findViewById(R.id.group_name);
+        assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
+        assertEquals(GONE, textView.getVisibility());
+        assertEquals(GONE, mNotificationInfo.findViewById(R.id.group_divider).getVisibility());
+    }
+
+    @Test
+    public void testBindNotification_noDelegate() {
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                null,
+                null,
+                null,
+                true);
+        final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
+        assertEquals(GONE, nameView.getVisibility());
+        final TextView dividerView = mNotificationInfo.findViewById(R.id.pkg_divider);
+        assertEquals(GONE, dividerView.getVisibility());
+    }
+
+    @Test
+    public void testBindNotification_delegate() throws Exception {
+        mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, "other", 0, null, TEST_UID, 0,
+                mSbn.getNotification(), UserHandle.CURRENT, null, 0);
+        final ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.uid = 7;  // non-zero
+        when(mMockPackageManager.getApplicationInfo(eq("other"), anyInt())).thenReturn(
+                applicationInfo);
+        when(mMockPackageManager.getApplicationLabel(any())).thenReturn("Other");
+
+        NotificationEntry entry = new NotificationEntryBuilder().setSbn(mSbn).build();
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                entry,
+                null,
+                null,
+                null,
+                true);
+        final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
+        assertEquals(VISIBLE, nameView.getVisibility());
+        assertTrue(nameView.getText().toString().contains("Proxied"));
+        final TextView dividerView = mNotificationInfo.findViewById(R.id.pkg_divider);
+        assertEquals(VISIBLE, dividerView.getVisibility());
+    }
+
+    @Test
+    public void testBindNotification_SetsOnClickListenerForSettings() {
+        final CountDownLatch latch = new CountDownLatch(1);
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                (View v, NotificationChannel c, int appUid) -> {
+                    assertEquals(mConversationChannel, c);
+                    latch.countDown();
+                },
+                null,
+                null,
+                true);
+
+        final View settingsButton = mNotificationInfo.findViewById(R.id.info);
+        settingsButton.performClick();
+        // Verify that listener was triggered.
+        assertEquals(0, latch.getCount());
+    }
+
+    @Test
+    public void testBindNotification_SettingsButtonInvisibleWhenNoClickListener() {
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                null,
+                null,
+                null,
+                true);
+        final View settingsButton = mNotificationInfo.findViewById(R.id.info);
+        assertTrue(settingsButton.getVisibility() != View.VISIBLE);
+    }
+
+    @Test
+    public void testBindNotification_SettingsButtonInvisibleWhenDeviceUnprovisioned() {
+        final CountDownLatch latch = new CountDownLatch(1);
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                (View v, NotificationChannel c, int appUid) -> {
+                    assertEquals(mNotificationChannel, c);
+                    latch.countDown();
+                },
+                null,
+                null,
+                false);
+        final View settingsButton = mNotificationInfo.findViewById(R.id.info);
+        assertTrue(settingsButton.getVisibility() != View.VISIBLE);
+    }
+
+    @Test
+    public void testBindNotification_bubbleActionVisibleWhenCanBubble()  {
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mBubbleEntry,
+                null,
+                null,
+                null,
+                true);
+
+        View bubbleView = mNotificationInfo.findViewById(R.id.bubble);
+        assertEquals(View.VISIBLE, bubbleView.getVisibility());
+    }
+
+    @Test
+    public void testBindNotification_bubbleActionVisibleWhenCannotBubble()  {
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                null,
+                null,
+                null,
+                true);
+
+        View bubbleView = mNotificationInfo.findViewById(R.id.bubble);
+        assertEquals(View.GONE, bubbleView.getVisibility());
+    }
+
+    @Test
+    public void testAddToHome() throws Exception {
+        when(mShortcutManager.isRequestPinShortcutSupported()).thenReturn(true);
+
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mBubbleEntry,
+                null,
+                null,
+                null,
+                true);
+
+
+        // Promote it
+        mNotificationInfo.findViewById(R.id.home).performClick();
+        mTestableLooper.processAllMessages();
+
+        verify(mShortcutManager, times(1)).requestPinShortcut(mShortcutInfo, null);
+        verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
+                anyString(), anyInt(), any());
+    }
+
+    @Test
+    public void testSnooze() throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
+
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mBubbleEntry,
+                null,
+                null,
+                (View v, int hours) -> {
+                    latch.countDown();
+                },
+                true);
+
+
+        // Promote it
+        mNotificationInfo.findViewById(R.id.snooze).performClick();
+        mTestableLooper.processAllMessages();
+
+        assertEquals(0, latch.getCount());
+        verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
+                anyString(), anyInt(), any());
+    }
+
+    @Test
+    public void testBubble_promotesBubble() throws Exception {
+        mNotificationChannel.setAllowBubbles(false);
+        mConversationChannel.setAllowBubbles(false);
+
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mBubbleEntry,
+                null,
+                null,
+                null,
+                true);
+
+        assertFalse(mBubbleEntry.isBubble());
+
+        // Promote it
+        mNotificationInfo.findViewById(R.id.bubble).performClick();
+        mTestableLooper.processAllMessages();
+
+        verify(mBubbleController, times(1)).onUserCreatedBubbleFromNotification(mBubbleEntry);
+        ArgumentCaptor<NotificationChannel> captor =
+                ArgumentCaptor.forClass(NotificationChannel.class);
+        verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+                anyString(), anyInt(), captor.capture());
+        assertTrue(captor.getValue().canBubble());
+    }
+
+    @Test
+    public void testBubble_demotesBubble() throws Exception {
+        mBubbleEntry.getSbn().getNotification().flags |= FLAG_BUBBLE;
+
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mBubbleEntry,
+                null,
+                null,
+                null,
+                true);
+
+        assertTrue(mBubbleEntry.isBubble());
+
+        // Demote it
+        mNotificationInfo.findViewById(R.id.bubble).performClick();
+        mTestableLooper.processAllMessages();
+
+        verify(mBubbleController, times(1)).onUserDemotedBubbleFromNotification(mBubbleEntry);
+        ArgumentCaptor<NotificationChannel> captor =
+                ArgumentCaptor.forClass(NotificationChannel.class);
+        verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+                anyString(), anyInt(), captor.capture());
+        assertFalse(captor.getValue().canBubble());
+    }
+
+    @Test
+    public void testFavorite_favorite() throws Exception {
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                null,
+                null,
+                null,
+                true);
+
+
+        Button fave = mNotificationInfo.findViewById(R.id.fave);
+        assertEquals(mContext.getString(R.string.notification_conversation_favorite),
+                fave.getText().toString());
+
+        fave.performClick();
+        mTestableLooper.processAllMessages();
+
+        ArgumentCaptor<NotificationChannel> captor =
+                ArgumentCaptor.forClass(NotificationChannel.class);
+        verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+                anyString(), anyInt(), captor.capture());
+        assertTrue(captor.getValue().canBypassDnd());
+    }
+
+    @Test
+    public void testFavorite_unfavorite() throws Exception {
+        mNotificationChannel.setBypassDnd(true);
+        mConversationChannel.setBypassDnd(true);
+
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                null,
+                null,
+                null,
+                true);
+
+        Button fave = mNotificationInfo.findViewById(R.id.fave);
+        assertEquals(mContext.getString(R.string.notification_conversation_unfavorite),
+                fave.getText().toString());
+
+        fave.performClick();
+        mTestableLooper.processAllMessages();
+
+        ArgumentCaptor<NotificationChannel> captor =
+                ArgumentCaptor.forClass(NotificationChannel.class);
+        verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+                anyString(), anyInt(), captor.capture());
+        assertFalse(captor.getValue().canBypassDnd());
+    }
+
+    @Test
+    public void testMute_mute() throws Exception {
+        mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
+        mConversationChannel.setImportance(IMPORTANCE_DEFAULT);
+
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                null,
+                null,
+                null,
+                true);
+
+        Button mute = mNotificationInfo.findViewById(R.id.mute);
+        assertEquals(mContext.getString(R.string.notification_conversation_mute),
+                mute.getText().toString());
+
+        mute.performClick();
+        mTestableLooper.processAllMessages();
+
+        ArgumentCaptor<NotificationChannel> captor =
+                ArgumentCaptor.forClass(NotificationChannel.class);
+        verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+                anyString(), anyInt(), captor.capture());
+        assertEquals(IMPORTANCE_LOW, captor.getValue().getImportance());
+    }
+
+    @Test
+    public void testMute_unmute() throws Exception {
+        mNotificationChannel.setImportance(IMPORTANCE_LOW);
+        mNotificationChannel.setOriginalImportance(IMPORTANCE_HIGH);
+        mConversationChannel.setImportance(IMPORTANCE_LOW);
+        mConversationChannel.setOriginalImportance(IMPORTANCE_HIGH);
+
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                null,
+                null,
+                null,
+                true);
+
+
+        Button mute = mNotificationInfo.findViewById(R.id.mute);
+        assertEquals(mContext.getString(R.string.notification_conversation_unmute),
+                mute.getText().toString());
+
+        mute.performClick();
+        mTestableLooper.processAllMessages();
+
+        ArgumentCaptor<NotificationChannel> captor =
+                ArgumentCaptor.forClass(NotificationChannel.class);
+        verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+                anyString(), anyInt(), captor.capture());
+        assertEquals(IMPORTANCE_HIGH, captor.getValue().getImportance());
+    }
+
+    @Test
+    public void testBindNotification_createsNewChannel() throws Exception {
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                null,
+                null,
+                null,
+                true);
+        verify(mMockINotificationManager, times(1)).createConversationNotificationChannelForPackage(
+                anyString(), anyInt(), any(), eq(CONVERSATION_ID));
+    }
+
+    @Test
+    public void testBindNotification_doesNotCreateNewChannelIfExists() throws Exception {
+        mNotificationChannel.setConversationId("", CONVERSATION_ID);
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                null,
+                null,
+                null,
+                true);
+        verify(mMockINotificationManager, never()).createConversationNotificationChannelForPackage(
+                anyString(), anyInt(), any(), eq(CONVERSATION_ID));
+    }
+
+    @Test
+    public void testAdjustImportanceTemporarilyAllowsReordering() {
+        mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
+        mConversationChannel.setImportance(IMPORTANCE_DEFAULT);
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                null,
+                null,
+                null,
+                true);
+
+        mNotificationInfo.findViewById(R.id.mute).performClick();
+
+        verify(mVisualStabilityManager).temporarilyAllowReordering();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
index f48c40c..b33d26f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
@@ -132,16 +132,6 @@
     }
 
     @Test
-    public void testNoAppOpsInSlowSwipe_biDirectionalSwipe() {
-        NotificationMenuRow row = new NotificationMenuRow(mContext, true);
-        row.createMenu(mRow, null);
-
-        ViewGroup container = (ViewGroup) row.getMenuView();
-        // in the new interruption model there is only the blocking item
-        assertEquals(1, container.getChildCount());
-    }
-
-    @Test
     public void testIsSnappedAndOnSameSide() {
         NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index 9cb06a5..13f3a5f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.statusbar.policy;
 
+import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
+import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNotNull;
 
@@ -39,6 +42,7 @@
 import android.os.Handler;
 import android.provider.Settings;
 import android.provider.Settings.Global;
+import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
@@ -358,7 +362,13 @@
     }
 
     public void updateDataConnectionState(int dataState, int dataNetType) {
-        when(mServiceState.getDataNetworkType()).thenReturn(dataNetType);
+        NetworkRegistrationInfo fakeRegInfo = new NetworkRegistrationInfo.Builder()
+                .setTransportType(TRANSPORT_TYPE_WWAN)
+                .setDomain(DOMAIN_PS)
+                .setAccessNetworkTechnology(dataNetType)
+                .build();
+        when(mServiceState.getNetworkRegistrationInfo(DOMAIN_PS, TRANSPORT_TYPE_WWAN))
+                .thenReturn(fakeRegInfo);
         mPhoneStateListener.onDataConnectionStateChanged(dataState, dataNetType);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
index 5a5ef8b..f6c750d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
@@ -1,5 +1,8 @@
 package com.android.systemui.statusbar.policy;
 
+import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
+import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
+
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.anyInt;
@@ -418,7 +421,13 @@
 
         // State from NR_5G to NONE NR_STATE_RESTRICTED, showing corresponding icon
         doReturn(NetworkRegistrationInfo.NR_STATE_RESTRICTED).when(mServiceState).getNrState();
-        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getDataNetworkType();
+        NetworkRegistrationInfo fakeRegInfo = new NetworkRegistrationInfo.Builder()
+                .setTransportType(TRANSPORT_TYPE_WWAN)
+                .setDomain(DOMAIN_PS)
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+                .build();
+        doReturn(fakeRegInfo).when(mServiceState)
+                .getNetworkRegistrationInfo(DOMAIN_PS, TRANSPORT_TYPE_WWAN);
         mPhoneStateListener.onDataConnectionStateChanged(TelephonyManager.DATA_CONNECTED,
                 TelephonyManager.NETWORK_TYPE_LTE);
 
@@ -480,8 +489,13 @@
 
         verifyDataIndicators(TelephonyIcons.ICON_LTE);
 
-        when(mServiceState.getDataNetworkType())
-                .thenReturn(TelephonyManager.NETWORK_TYPE_HSPA);
+        NetworkRegistrationInfo fakeRegInfo = new NetworkRegistrationInfo.Builder()
+                .setTransportType(TRANSPORT_TYPE_WWAN)
+                .setDomain(DOMAIN_PS)
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_HSPA)
+                .build();
+        when(mServiceState.getNetworkRegistrationInfo(DOMAIN_PS, TRANSPORT_TYPE_WWAN))
+                .thenReturn(fakeRegInfo);
         updateServiceState();
         verifyDataIndicators(TelephonyIcons.ICON_H);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt
index 709a1a8..a785d4b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt
@@ -19,6 +19,7 @@
 import org.junit.After
 import org.junit.Assert
 import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
 import org.junit.Assert.assertNotEquals
 import org.junit.Assert.assertTrue
 import org.junit.Before
@@ -154,6 +155,7 @@
         verify(mockEndListener).onAnimationEnd(
                 testView,
                 DynamicAnimation.TRANSLATION_X,
+                wasFling = false,
                 canceled = false,
                 finalValue = 10f,
                 finalVelocity = 0f,
@@ -175,6 +177,7 @@
         verify(mockEndListener).onAnimationEnd(
                 testView,
                 DynamicAnimation.TRANSLATION_Y,
+                wasFling = false,
                 canceled = false,
                 finalValue = 500f,
                 finalVelocity = 0f,
@@ -214,8 +217,8 @@
 
         verifyUpdateListenerCalls(animator, mockUpdateListener)
         verify(mockEndListener, times(1)).onAnimationEnd(
-                eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), anyFloat(), anyFloat(),
-                eq(true))
+                eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), eq(false), anyFloat(),
+                anyFloat(), eq(true))
         verify(mockEndAction, times(1)).run()
 
         animator
@@ -329,13 +332,24 @@
         // The original end listener should also have been called, with allEnded = true since it was
         // provided to an animator that animated only TRANSLATION_X.
         verify(mockEndListener, times(1))
-                .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_X, false, 200f, 0f, true)
+                .onAnimationEnd(
+                        testView, DynamicAnimation.TRANSLATION_X,
+                        wasFling = false,
+                        canceled = false,
+                        finalValue = 200f,
+                        finalVelocity = 0f,
+                        allRelevantPropertyAnimsEnded = true)
         verifyNoMoreInteractions(mockEndListener)
 
         // The second end listener should have been called, but with allEnded = false since it was
         // provided to an animator that animated both TRANSLATION_X and TRANSLATION_Y.
         verify(secondEndListener, times(1))
-                .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_X, false, 200f, 0f, false)
+                .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_X,
+                        wasFling = false,
+                        canceled = false,
+                        finalValue = 200f,
+                        finalVelocity = 0f,
+                        allRelevantPropertyAnimsEnded = false)
         verifyNoMoreInteractions(secondEndListener)
 
         PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(animator, DynamicAnimation.TRANSLATION_Y)
@@ -345,7 +359,12 @@
         verifyNoMoreInteractions(mockEndListener)
 
         verify(secondEndListener, times(1))
-                .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_Y, false, 4000f, 0f, true)
+                .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_Y,
+                        wasFling = false,
+                        canceled = false,
+                        finalValue = 4000f,
+                        finalVelocity = 0f,
+                        allRelevantPropertyAnimsEnded = true)
         verifyNoMoreInteractions(secondEndListener)
     }
 
@@ -364,8 +383,8 @@
         assertEquals(10f, testView.translationX, 1f)
         verify(mockEndListener, times(1))
                 .onAnimationEnd(
-                        eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), eq(10f),
-                        anyFloat(), eq(true))
+                        eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(true), eq(false),
+                        eq(10f), anyFloat(), eq(true))
 
         animator
                 .fling(
@@ -381,8 +400,39 @@
         assertEquals(-5f, testView.translationX, 1f)
         verify(mockEndListener, times(1))
                 .onAnimationEnd(
-                        eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), eq(-5f),
-                        anyFloat(), eq(true))
+                        eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(true), eq(false),
+                        eq(-5f), anyFloat(), eq(true))
+    }
+
+    @Test
+    fun testIsPropertyAnimating() {
+        PhysicsAnimatorTestUtils.setAllAnimationsBlock(false)
+
+        testView.physicsAnimator
+                .spring(DynamicAnimation.TRANSLATION_X, 500f, springConfig)
+                .fling(DynamicAnimation.TRANSLATION_Y, 10f, flingConfig)
+                .spring(DynamicAnimation.TRANSLATION_Z, 1000f, springConfig)
+                .start()
+
+        // All of the properties we just started should be animating.
+        assertTrue(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_X))
+        assertTrue(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Y))
+        assertTrue(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Z))
+
+        // Block until x and y end.
+        PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(testView.physicsAnimator,
+                DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y)
+
+        // Verify that x and y are no longer animating, but that Z is (it's springing to 1000f).
+        assertFalse(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_X))
+        assertFalse(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Y))
+        assertTrue(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Z))
+
+        PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Z)
+
+        assertFalse(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_X))
+        assertFalse(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Y))
+        assertFalse(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Z))
     }
 
     @Test
@@ -395,6 +445,118 @@
         assertEquals(200f, testView.translationX, 1f)
     }
 
+    @Test
+    fun testFlingThenSpring() {
+        PhysicsAnimatorTestUtils.setAllAnimationsBlock(false)
+
+        // Start at 500f and fling hard to the left. We should quickly reach the 250f minimum, fly
+        // past it since there's so much velocity remaining, then spring back to 250f.
+        testView.translationX = 500f
+        animator
+                .flingThenSpring(
+                        DynamicAnimation.TRANSLATION_X,
+                        -5000f,
+                        flingConfig.apply { min = 250f },
+                        springConfig)
+                .addUpdateListener(mockUpdateListener)
+                .addEndListener(mockEndListener)
+                .withEndActions(mockEndAction::run)
+                .start()
+
+        // Block until we pass the minimum.
+        PhysicsAnimatorTestUtils.blockUntilFirstAnimationFrameWhereTrue(
+                animator) { v -> v.translationX <= 250f }
+
+        // Double check that the view is there.
+        assertTrue(testView.translationX <= 250f)
+
+        // The update listener should have been called with a value < 500f, and then a value less
+        // than or equal to the 250f minimum.
+        verifyAnimationUpdateFrames(animator, DynamicAnimation.TRANSLATION_X,
+                { u -> u.value < 500f },
+                { u -> u.value <= 250f })
+
+        // Despite the fact that the fling has ended, the end listener shouldn't have been called
+        // since we're about to begin springing the same property.
+        verifyNoMoreInteractions(mockEndListener)
+        verifyNoMoreInteractions(mockEndAction)
+
+        // Wait for the spring to finish.
+        PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_X)
+
+        // Make sure we continued past 250f since the spring should have been started with some
+        // remaining negative velocity from the fling.
+        verifyAnimationUpdateFrames(animator, DynamicAnimation.TRANSLATION_X,
+                { u -> u.value < 250f })
+
+        // At this point, the animation end listener should have been called once, and only once,
+        // when the spring ended at 250f.
+        verify(mockEndListener).onAnimationEnd(testView, DynamicAnimation.TRANSLATION_X,
+                wasFling = false,
+                canceled = false,
+                finalValue = 250f,
+                finalVelocity = 0f,
+                allRelevantPropertyAnimsEnded = true)
+        verifyNoMoreInteractions(mockEndListener)
+
+        // The end action should also have been called once.
+        verify(mockEndAction, times(1)).run()
+        verifyNoMoreInteractions(mockEndAction)
+
+        assertEquals(250f, testView.translationX)
+    }
+
+    @Test
+    fun testFlingThenSpring_objectOutsideFlingBounds() {
+        // Start the view at x = -500, well outside the fling bounds of min = 0f, with strong
+        // negative velocity.
+        testView.translationX = -500f
+        animator
+                .flingThenSpring(
+                        DynamicAnimation.TRANSLATION_X,
+                        -5000f,
+                        flingConfig.apply { min = 0f },
+                        springConfig)
+                .addUpdateListener(mockUpdateListener)
+                .addEndListener(mockEndListener)
+                .withEndActions(mockEndAction::run)
+                .start()
+
+        // The initial -5000f velocity should result in frames to the left of -500f before the view
+        // springs back towards 0f.
+        verifyAnimationUpdateFrames(
+                animator, DynamicAnimation.TRANSLATION_X,
+                { u -> u.value < -500f },
+                { u -> u.value > -500f })
+
+        // We should end up at the fling min.
+        assertEquals(0f, testView.translationX, 1f)
+    }
+
+    @Test
+    fun testFlingToMinMaxThenSpring() {
+        // Start at x = 500f.
+        testView.translationX = 500f
+
+        // Fling to the left at the very sad rate of -1 pixels per second. That won't get us much of
+        // anywhere, and certainly not to the 0f min.
+        animator
+                // Good thing we have flingToMinMaxThenSpring!
+                .flingThenSpring(
+                        DynamicAnimation.TRANSLATION_X,
+                        -10000f,
+                        flingConfig.apply { min = 0f },
+                        springConfig,
+                        flingMustReachMinOrMax = true)
+                .addUpdateListener(mockUpdateListener)
+                .addEndListener(mockEndListener)
+                .withEndActions(mockEndAction::run)
+                .start()
+
+        // Thanks, flingToMinMaxThenSpring, for adding enough velocity to get us here.
+        assertEquals(0f, testView.translationX, 1f)
+    }
+
     /**
      * Verifies that the calls to the mock update listener match the animation update frames
      * reported by the test internal listener, in order.
diff --git a/packages/Tethering/Android.bp b/packages/Tethering/Android.bp
index d297f3f..e441fb5 100644
--- a/packages/Tethering/Android.bp
+++ b/packages/Tethering/Android.bp
@@ -16,7 +16,7 @@
 
 java_defaults {
     name: "TetheringAndroidLibraryDefaults",
-    platform_apis: true,
+    sdk_version: "system_current",
     srcs: [
         "src/**/*.java",
         ":framework-tethering-shared-srcs",
@@ -29,6 +29,7 @@
         "netlink-client",
         "networkstack-aidl-interfaces-unstable-java",
         "android.hardware.tetheroffload.control-V1.0-java",
+        "net-utils-framework-common",
     ],
     libs: [
         "framework-tethering",
@@ -80,7 +81,7 @@
 // Common defaults for compiling the actual APK.
 java_defaults {
     name: "TetheringAppDefaults",
-    platform_apis: true,
+    sdk_version: "system_current",
     privileged: true,
     // Build system doesn't track transitive dependeicies for jni_libs, list all the dependencies
     // explicitly.
diff --git a/packages/Tethering/AndroidManifest.xml b/packages/Tethering/AndroidManifest.xml
index 5a71eb2..c71d0d7 100644
--- a/packages/Tethering/AndroidManifest.xml
+++ b/packages/Tethering/AndroidManifest.xml
@@ -36,6 +36,7 @@
     <uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" />
     <uses-permission android:name="android.permission.TETHER_PRIVILEGED" />
     <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
+    <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
     <uses-permission android:name="android.permission.WRITE_SETTINGS" />
 
     <application
diff --git a/packages/Tethering/res/values/config.xml b/packages/Tethering/res/values/config.xml
index ca290c6..19e8d69 100644
--- a/packages/Tethering/res/values/config.xml
+++ b/packages/Tethering/res/values/config.xml
@@ -38,8 +38,9 @@
 
     <!-- List of regexpressions describing the interface (if any) that represent tetherable
          Wifi P2P interfaces.  If the device doesn't want to support tethering over Wifi P2p this
-         should be empty.  An example would be "p2p-p2p.*" -->
+         should be empty.  An example would be "p2p-p2p\\d-.*" -->
     <string-array translatable="false" name="config_tether_wifi_p2p_regexs">
+        <item>"p2p-p2p\\d-.*"</item>
     </string-array>
 
     <!-- List of regexpressions describing the interface (if any) that represent tetherable
@@ -100,7 +101,7 @@
 
     <!-- If the mobile hotspot feature requires provisioning, a package name and class name
          can be provided to launch a supported application that provisions the devices.
-         EntitlementManager will send an inent to Settings with the specified package name and
+         EntitlementManager will send an intent to Settings with the specified package name and
          class name in extras to launch provision app.
          TODO: note what extras here.
 
diff --git a/packages/Tethering/src/android/net/ip/IpServer.java b/packages/Tethering/src/android/net/ip/IpServer.java
index 6ac467e..4306cf0b 100644
--- a/packages/Tethering/src/android/net/ip/IpServer.java
+++ b/packages/Tethering/src/android/net/ip/IpServer.java
@@ -26,7 +26,6 @@
 
 import android.net.INetd;
 import android.net.INetworkStackStatusCallback;
-import android.net.INetworkStatsService;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
@@ -176,7 +175,6 @@
 
     private final SharedLog mLog;
     private final INetd mNetd;
-    private final INetworkStatsService mStatsService;
     private final Callback mCallback;
     private final InterfaceController mInterfaceCtrl;
 
@@ -208,12 +206,10 @@
 
     public IpServer(
             String ifaceName, Looper looper, int interfaceType, SharedLog log,
-            INetd netd, INetworkStatsService statsService, Callback callback,
-            boolean usingLegacyDhcp, Dependencies deps) {
+            INetd netd, Callback callback, boolean usingLegacyDhcp, Dependencies deps) {
         super(ifaceName, looper);
         mLog = log.forSubComponent(ifaceName);
         mNetd = netd;
-        mStatsService = statsService;
         mCallback = callback;
         mInterfaceCtrl = new InterfaceController(ifaceName, mNetd, mLog);
         mIfaceName = ifaceName;
@@ -882,12 +878,6 @@
             // to remove their rules, which generates errors.
             // Just do the best we can.
             try {
-                // About to tear down NAT; gather remaining statistics.
-                mStatsService.forceUpdate();
-            } catch (Exception e) {
-                mLog.e("Exception in forceUpdate: " + e.toString());
-            }
-            try {
                 mNetd.ipfwdRemoveInterfaceForward(mIfaceName, upstreamIface);
             } catch (RemoteException | ServiceSpecificException e) {
                 mLog.e("Exception in ipfwdRemoveInterfaceForward: " + e.toString());
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java b/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java
index 4e2a2c1..1cabc8d 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java
@@ -27,8 +27,6 @@
 import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
 import static android.net.TetheringManager.TETHER_ERROR_PROVISION_FAILED;
 
-import static com.android.internal.R.string.config_wifi_tether_enable;
-
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
@@ -36,7 +34,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.res.Resources;
 import android.net.util.SharedLog;
 import android.os.Bundle;
 import android.os.Handler;
@@ -54,6 +51,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.StateMachine;
+import com.android.networkstack.tethering.R;
 
 import java.io.PrintWriter;
 
@@ -75,9 +73,7 @@
             "com.android.server.connectivity.tethering.PROVISIONING_RECHECK_ALARM";
     private static final String EXTRA_SUBID = "subId";
 
-    // {@link ComponentName} of the Service used to run tether provisioning.
-    private static final ComponentName TETHER_SERVICE = ComponentName.unflattenFromString(
-            Resources.getSystem().getString(config_wifi_tether_enable));
+    private final ComponentName mSilentProvisioningService;
     private static final int MS_PER_HOUR = 60 * 60 * 1000;
     private static final int EVENT_START_PROVISIONING = 0;
     private static final int EVENT_STOP_PROVISIONING = 1;
@@ -122,6 +118,8 @@
         mHandler = new EntitlementHandler(masterHandler.getLooper());
         mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_PROVISIONING_ALARM),
                 null, mHandler);
+        mSilentProvisioningService = ComponentName.unflattenFromString(
+                mContext.getResources().getString(R.string.config_wifi_tether_enable));
     }
 
     public void setOnUiEntitlementFailedListener(final OnUiEntitlementFailedListener listener) {
@@ -377,7 +375,7 @@
         intent.putExtra(EXTRA_RUN_PROVISION, true);
         intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver);
         intent.putExtra(EXTRA_SUBID, subId);
-        intent.setComponent(TETHER_SERVICE);
+        intent.setComponent(mSilentProvisioningService);
         // Only admin user can change tethering and SilentTetherProvisioning don't need to
         // show UI, it is fine to always start setting's background service as system user.
         mContext.startService(intent);
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadController.java b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadController.java
index ce7c2a6..cc36f4a 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadController.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadController.java
@@ -16,35 +16,40 @@
 
 package com.android.server.connectivity.tethering;
 
+import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
+import static android.net.NetworkStats.METERED_NO;
+import static android.net.NetworkStats.ROAMING_NO;
 import static android.net.NetworkStats.SET_DEFAULT;
-import static android.net.NetworkStats.STATS_PER_UID;
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
-import static android.net.TrafficStats.UID_TETHERING;
+import static android.net.NetworkStats.UID_TETHERING;
 import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.usage.NetworkStatsManager;
 import android.content.ContentResolver;
-import android.net.ITetheringStatsProvider;
 import android.net.InetAddresses;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.NetworkStats;
+import android.net.NetworkStats.Entry;
 import android.net.RouteInfo;
 import android.net.netlink.ConntrackMessage;
 import android.net.netlink.NetlinkConstants;
 import android.net.netlink.NetlinkSocket;
+import android.net.netstats.provider.AbstractNetworkStatsProvider;
+import android.net.netstats.provider.NetworkStatsProviderCallback;
 import android.net.util.SharedLog;
 import android.os.Handler;
-import android.os.INetworkManagementService;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.SystemClock;
 import android.provider.Settings;
 import android.system.ErrnoException;
 import android.system.OsConstants;
 import android.text.TextUtils;
+import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.connectivity.tethering.OffloadHardwareInterface.ForwardedStats;
 
@@ -73,13 +78,19 @@
     private static final String ANYIP = "0.0.0.0";
     private static final ForwardedStats EMPTY_STATS = new ForwardedStats();
 
+    @VisibleForTesting
+    enum StatsType {
+        STATS_PER_IFACE,
+        STATS_PER_UID,
+    }
+
     private enum UpdateType { IF_NEEDED, FORCE };
 
     private final Handler mHandler;
     private final OffloadHardwareInterface mHwInterface;
     private final ContentResolver mContentResolver;
-    private final INetworkManagementService mNms;
-    private final ITetheringStatsProvider mStatsProvider;
+    private final @NonNull OffloadTetheringStatsProvider mStatsProvider;
+    private final @Nullable NetworkStatsProviderCallback mStatsProviderCb;
     private final SharedLog mLog;
     private final HashMap<String, LinkProperties> mDownstreams;
     private boolean mConfigInitialized;
@@ -109,22 +120,23 @@
     private int mNatUpdateNetlinkErrors;
 
     public OffloadController(Handler h, OffloadHardwareInterface hwi,
-            ContentResolver contentResolver, INetworkManagementService nms, SharedLog log) {
+            ContentResolver contentResolver, NetworkStatsManager nsm, SharedLog log) {
         mHandler = h;
         mHwInterface = hwi;
         mContentResolver = contentResolver;
-        mNms = nms;
         mStatsProvider = new OffloadTetheringStatsProvider();
         mLog = log.forSubComponent(TAG);
         mDownstreams = new HashMap<>();
         mExemptPrefixes = new HashSet<>();
         mLastLocalPrefixStrs = new HashSet<>();
-
+        NetworkStatsProviderCallback providerCallback = null;
         try {
-            mNms.registerTetheringStatsProvider(mStatsProvider, getClass().getSimpleName());
-        } catch (RemoteException e) {
-            mLog.e("Cannot register offload stats provider: " + e);
+            providerCallback = nsm.registerNetworkStatsProvider(
+                    getClass().getSimpleName(), mStatsProvider);
+        } catch (RuntimeException e) {
+            Log.wtf(TAG, "Cannot register offload stats provider: " + e);
         }
+        mStatsProviderCb = providerCallback;
     }
 
     /** Start hardware offload. */
@@ -173,7 +185,7 @@
                         // and we need to synchronize stats and limits between
                         // software and hardware forwarding.
                         updateStatsForAllUpstreams();
-                        forceTetherStatsPoll();
+                        mStatsProvider.pushTetherStats();
                     }
 
                     @Override
@@ -186,7 +198,7 @@
                         // limits set take into account any software tethering
                         // traffic that has been happening in the meantime.
                         updateStatsForAllUpstreams();
-                        forceTetherStatsPoll();
+                        mStatsProvider.pushTetherStats();
                         // [2] (Re)Push all state.
                         computeAndPushLocalPrefixes(UpdateType.FORCE);
                         pushAllDownstreamState();
@@ -204,14 +216,11 @@
                         // the HAL queued the callback.
                         // TODO: rev the HAL so that it provides an interface name.
 
-                        // Fetch current stats, so that when our notification reaches
-                        // NetworkStatsService and triggers a poll, we will respond with
-                        // current data (which will be above the limit that was reached).
-                        // Note that if we just changed upstream, this is unnecessary but harmless.
-                        // The stats for the previous upstream were already updated on this thread
-                        // just after the upstream was changed, so they are also up-to-date.
                         updateStatsForCurrentUpstream();
-                        forceTetherStatsPoll();
+                        mStatsProvider.pushTetherStats();
+                        // Push stats to service does not cause the service react to it immediately.
+                        // Inform the service about limit reached.
+                        if (mStatsProviderCb != null) mStatsProviderCb.onLimitReached();
                     }
 
                     @Override
@@ -253,42 +262,37 @@
         return mConfigInitialized && mControlInitialized;
     }
 
-    private class OffloadTetheringStatsProvider extends ITetheringStatsProvider.Stub {
-        @Override
-        public NetworkStats getTetherStats(int how) {
-            // getTetherStats() is the only function in OffloadController that can be called from
-            // a different thread. Do not attempt to update stats by querying the offload HAL
-            // synchronously from a different thread than our Handler thread. http://b/64771555.
-            Runnable updateStats = () -> {
-                updateStatsForCurrentUpstream();
-            };
-            if (Looper.myLooper() == mHandler.getLooper()) {
-                updateStats.run();
-            } else {
-                mHandler.post(updateStats);
-            }
+    @VisibleForTesting
+    class OffloadTetheringStatsProvider extends AbstractNetworkStatsProvider {
+        // These stats must only ever be touched on the handler thread.
+        @NonNull
+        private NetworkStats mIfaceStats = new NetworkStats(0L, 0);
+        @NonNull
+        private NetworkStats mUidStats = new NetworkStats(0L, 0);
 
-            NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0);
-            NetworkStats.Entry entry = new NetworkStats.Entry();
-            entry.set = SET_DEFAULT;
-            entry.tag = TAG_NONE;
-            entry.uid = (how == STATS_PER_UID) ? UID_TETHERING : UID_ALL;
+        @VisibleForTesting
+        @NonNull
+        NetworkStats getTetherStats(@NonNull StatsType how) {
+            NetworkStats stats = new NetworkStats(0L, 0);
+            final int uid = (how == StatsType.STATS_PER_UID) ? UID_TETHERING : UID_ALL;
 
-            for (Map.Entry<String, ForwardedStats> kv : mForwardedStats.entrySet()) {
-                ForwardedStats value = kv.getValue();
-                entry.iface = kv.getKey();
-                entry.rxBytes = value.rxBytes;
-                entry.txBytes = value.txBytes;
-                stats.addEntry(entry);
+            for (final Map.Entry<String, ForwardedStats> kv : mForwardedStats.entrySet()) {
+                final ForwardedStats value = kv.getValue();
+                final Entry entry = new Entry(kv.getKey(), uid, SET_DEFAULT, TAG_NONE, METERED_NO,
+                        ROAMING_NO, DEFAULT_NETWORK_NO, value.rxBytes, 0L, value.txBytes, 0L, 0L);
+                stats = stats.addValues(entry);
             }
 
             return stats;
         }
 
         @Override
-        public void setInterfaceQuota(String iface, long quotaBytes) {
+        public void setLimit(String iface, long quotaBytes) {
+            mLog.i("setLimit: " + iface + "," + quotaBytes);
+            // Listen for all iface is necessary since upstream might be changed after limit
+            // is set.
             mHandler.post(() -> {
-                if (quotaBytes == ITetheringStatsProvider.QUOTA_UNLIMITED) {
+                if (quotaBytes == QUOTA_UNLIMITED) {
                     mInterfaceQuotas.remove(iface);
                 } else {
                     mInterfaceQuotas.put(iface, quotaBytes);
@@ -296,6 +300,42 @@
                 maybeUpdateDataLimit(iface);
             });
         }
+
+        /**
+         * Push stats to service, but does not cause a force polling. Note that this can only be
+         * called on the handler thread.
+         */
+        public void pushTetherStats() {
+            // TODO: remove the accumulated stats and report the diff from HAL directly.
+            if (null == mStatsProviderCb) return;
+            final NetworkStats ifaceDiff =
+                    getTetherStats(StatsType.STATS_PER_IFACE).subtract(mIfaceStats);
+            final NetworkStats uidDiff =
+                    getTetherStats(StatsType.STATS_PER_UID).subtract(mUidStats);
+            try {
+                mStatsProviderCb.onStatsUpdated(0 /* token */, ifaceDiff, uidDiff);
+                mIfaceStats = mIfaceStats.add(ifaceDiff);
+                mUidStats = mUidStats.add(uidDiff);
+            } catch (RuntimeException e) {
+                mLog.e("Cannot report network stats: ", e);
+            }
+        }
+
+        @Override
+        public void requestStatsUpdate(int token) {
+            mLog.i("requestStatsUpdate: " + token);
+            // Do not attempt to update stats by querying the offload HAL
+            // synchronously from a different thread than the Handler thread. http://b/64771555.
+            mHandler.post(() -> {
+                updateStatsForCurrentUpstream();
+                pushTetherStats();
+            });
+        }
+
+        @Override
+        public void setAlert(long quotaBytes) {
+            // TODO: Ask offload HAL to notify alert without stopping traffic.
+        }
     }
 
     private String currentUpstreamInterface() {
@@ -353,14 +393,6 @@
         }
     }
 
-    private void forceTetherStatsPoll() {
-        try {
-            mNms.tetherLimitReached(mStatsProvider);
-        } catch (RemoteException e) {
-            mLog.e("Cannot report data limit reached: " + e);
-        }
-    }
-
     /** Set current tethering upstream LinkProperties. */
     public void setUpstreamLinkProperties(LinkProperties lp) {
         if (!started() || Objects.equals(mUpstreamLinkProperties, lp)) return;
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
index 4a8ef1f..90b9d3f 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
@@ -29,6 +29,8 @@
 import android.os.RemoteException;
 import android.system.OsConstants;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.util.ArrayList;
 
 
@@ -91,6 +93,12 @@
             txBytes = 0;
         }
 
+        @VisibleForTesting
+        public ForwardedStats(long rxBytes, long txBytes) {
+            this.rxBytes = rxBytes;
+            this.txBytes = txBytes;
+        }
+
         /** Add Tx/Rx bytes. */
         public void add(ForwardedStats other) {
             rxBytes += other.rxBytes;
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
index 038d7ae..37e0cc1 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
@@ -20,6 +20,7 @@
 import static android.hardware.usb.UsbManager.USB_CONFIGURED;
 import static android.hardware.usb.UsbManager.USB_CONNECTED;
 import static android.hardware.usb.UsbManager.USB_FUNCTION_RNDIS;
+import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
 import static android.net.ConnectivityManager.EXTRA_NETWORK_INFO;
 import static android.net.TetheringManager.ACTION_TETHER_STATE_CHANGED;
@@ -53,6 +54,7 @@
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.app.usage.NetworkStatsManager;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothPan;
 import android.bluetooth.BluetoothProfile;
@@ -63,9 +65,8 @@
 import android.content.IntentFilter;
 import android.content.res.Resources;
 import android.hardware.usb.UsbManager;
+import android.net.ConnectivityManager;
 import android.net.INetd;
-import android.net.INetworkPolicyManager;
-import android.net.INetworkStatsService;
 import android.net.ITetheringEventCallback;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
@@ -176,8 +177,6 @@
     private final Context mContext;
     private final ArrayMap<String, TetherState> mTetherStates;
     private final BroadcastReceiver mStateReceiver;
-    private final INetworkStatsService mStatsService;
-    private final INetworkPolicyManager mPolicyManager;
     private final Looper mLooper;
     private final StateMachine mTetherMasterSM;
     private final OffloadController mOffloadController;
@@ -207,13 +206,12 @@
     private boolean mWifiTetherRequested;
     private Network mTetherUpstream;
     private TetherStatesParcel mTetherStatesParcel;
+    private boolean mDataSaverEnabled = false;
 
     public Tethering(TetheringDependencies deps) {
         mLog.mark("Tethering.constructed");
         mDeps = deps;
         mContext = mDeps.getContext();
-        mStatsService = mDeps.getINetworkStatsService();
-        mPolicyManager = mDeps.getINetworkPolicyManager();
         mNetd = mDeps.getINetd(mContext);
         mLooper = mDeps.getTetheringLooper();
 
@@ -224,10 +222,12 @@
         mTetherMasterSM = new TetherMasterSM("TetherMaster", mLooper, deps);
         mTetherMasterSM.start();
 
+        final NetworkStatsManager statsManager =
+                (NetworkStatsManager) mContext.getSystemService(Context.NETWORK_STATS_SERVICE);
         mHandler = mTetherMasterSM.getHandler();
         mOffloadController = new OffloadController(mHandler,
                 mDeps.getOffloadHardwareInterface(mHandler, mLog), mContext.getContentResolver(),
-                mDeps.getINetworkManagementService(), mLog);
+                statsManager, mLog);
         mUpstreamNetworkMonitor = mDeps.getUpstreamNetworkMonitor(mContext, mTetherMasterSM, mLog,
                 TetherMasterSM.EVENT_UPSTREAM_CALLBACK);
         mForwardedDownstreams = new HashSet<>();
@@ -264,7 +264,7 @@
         }
 
         final UserManager userManager = (UserManager) mContext.getSystemService(
-                    Context.USER_SERVICE);
+                Context.USER_SERVICE);
         mTetheringRestriction = new UserRestrictionActionListener(userManager, this);
         final TetheringThreadExecutor executor = new TetheringThreadExecutor(mHandler);
         mActiveDataSubIdListener = new ActiveDataSubIdListener(executor);
@@ -288,6 +288,7 @@
         filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
         filter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
         filter.addAction(UserManager.ACTION_USER_RESTRICTIONS_CHANGED);
+        filter.addAction(ACTION_RESTRICT_BACKGROUND_CHANGED);
         mContext.registerReceiver(mStateReceiver, filter, null, handler);
     }
 
@@ -484,7 +485,7 @@
     }
 
     private void setBluetoothTethering(final boolean enable, final ResultReceiver receiver) {
-        final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        final BluetoothAdapter adapter = mDeps.getBluetoothAdapter();
         if (adapter == null || !adapter.isEnabled()) {
             Log.w(TAG, "Tried to enable bluetooth tethering with null or disabled adapter. null: "
                     + (adapter == null));
@@ -740,8 +741,7 @@
                 .setContentIntent(pi);
         mLastNotificationId = id;
 
-        notificationManager.notify(null, mLastNotificationId,
-                mTetheredNotificationBuilder.buildInto(new Notification()));
+        notificationManager.notify(null, mLastNotificationId, mTetheredNotificationBuilder.build());
     }
 
     @VisibleForTesting
@@ -775,6 +775,9 @@
             } else if (action.equals(UserManager.ACTION_USER_RESTRICTIONS_CHANGED)) {
                 mLog.log("OBSERVED user restrictions changed");
                 handleUserRestrictionAction();
+            } else if (action.equals(ACTION_RESTRICT_BACKGROUND_CHANGED)) {
+                mLog.log("OBSERVED data saver changed");
+                handleDataSaverChanged();
             }
         }
 
@@ -885,6 +888,20 @@
         private void handleUserRestrictionAction() {
             mTetheringRestriction.onUserRestrictionsChanged();
         }
+
+        private void handleDataSaverChanged() {
+            final ConnectivityManager connMgr = (ConnectivityManager) mContext.getSystemService(
+                    Context.CONNECTIVITY_SERVICE);
+            final boolean isDataSaverEnabled = connMgr.getRestrictBackgroundStatus()
+                    != ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
+
+            if (mDataSaverEnabled == isDataSaverEnabled) return;
+
+            mDataSaverEnabled = isDataSaverEnabled;
+            if (mDataSaverEnabled) {
+                untetherAll();
+            }
+        }
     }
 
     @VisibleForTesting
@@ -1982,15 +1999,6 @@
 
         mLog.log(String.format("OBSERVED iface=%s state=%s error=%s", iface, state, error));
 
-        try {
-            // Notify that we're tethering (or not) this interface.
-            // This is how data saver for instance knows if the user explicitly
-            // turned on tethering (thus keeping us from being in data saver mode).
-            mPolicyManager.onTetheringChanged(iface, state == IpServer.STATE_TETHERED);
-        } catch (RemoteException e) {
-            // Not really very much we can do here.
-        }
-
         // If TetherMasterSM is in ErrorState, TetherMasterSM stays there.
         // Thus we give a chance for TetherMasterSM to recover to InitialState
         // by sending CMD_CLEAR_ERROR
@@ -2054,7 +2062,7 @@
 
         mLog.log("adding TetheringInterfaceStateMachine for: " + iface);
         final TetherState tetherState = new TetherState(
-                new IpServer(iface, mLooper, interfaceType, mLog, mNetd, mStatsService,
+                new IpServer(iface, mLooper, interfaceType, mLog, mNetd,
                              makeControlCallback(), mConfig.enableLegacyDhcpServer,
                              mDeps.getIpServerDependencies()));
         mTetherStates.put(iface, tetherState);
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java
index dbe7892..068c346 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java
@@ -23,18 +23,6 @@
 import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
 import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
 
-import static com.android.internal.R.array.config_mobile_hotspot_provision_app;
-import static com.android.internal.R.array.config_tether_bluetooth_regexs;
-import static com.android.internal.R.array.config_tether_dhcp_range;
-import static com.android.internal.R.array.config_tether_upstream_types;
-import static com.android.internal.R.array.config_tether_usb_regexs;
-import static com.android.internal.R.array.config_tether_wifi_p2p_regexs;
-import static com.android.internal.R.array.config_tether_wifi_regexs;
-import static com.android.internal.R.bool.config_tether_upstream_automatic;
-import static com.android.internal.R.integer.config_mobile_hotspot_provision_check_period;
-import static com.android.internal.R.string.config_mobile_hotspot_provision_app_no_ui;
-import static com.android.networkstack.tethering.R.bool.config_tether_enable_legacy_dhcp_server;
-
 import android.content.Context;
 import android.content.res.Resources;
 import android.net.TetheringConfigurationParcel;
@@ -45,6 +33,7 @@
 import android.text.TextUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.networkstack.tethering.R;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -113,27 +102,30 @@
         activeDataSubId = id;
         Resources res = getResources(ctx, activeDataSubId);
 
-        tetherableUsbRegexs = getResourceStringArray(res, config_tether_usb_regexs);
+        tetherableUsbRegexs = getResourceStringArray(res, R.array.config_tether_usb_regexs);
         // TODO: Evaluate deleting this altogether now that Wi-Fi always passes
         // us an interface name. Careful consideration needs to be given to
         // implications for Settings and for provisioning checks.
-        tetherableWifiRegexs = getResourceStringArray(res, config_tether_wifi_regexs);
-        tetherableWifiP2pRegexs = getResourceStringArray(res, config_tether_wifi_p2p_regexs);
-        tetherableBluetoothRegexs = getResourceStringArray(res, config_tether_bluetooth_regexs);
+        tetherableWifiRegexs = getResourceStringArray(res, R.array.config_tether_wifi_regexs);
+        tetherableWifiP2pRegexs = getResourceStringArray(
+                res, R.array.config_tether_wifi_p2p_regexs);
+        tetherableBluetoothRegexs = getResourceStringArray(
+                res, R.array.config_tether_bluetooth_regexs);
 
         isDunRequired = checkDunRequired(ctx);
 
-        chooseUpstreamAutomatically = getResourceBoolean(res, config_tether_upstream_automatic);
+        chooseUpstreamAutomatically = getResourceBoolean(
+                res, R.bool.config_tether_upstream_automatic);
         preferredUpstreamIfaceTypes = getUpstreamIfaceTypes(res, isDunRequired);
 
         legacyDhcpRanges = getLegacyDhcpRanges(res);
         defaultIPv4DNS = copy(DEFAULT_IPV4_DNS);
         enableLegacyDhcpServer = getEnableLegacyDhcpServer(res);
 
-        provisioningApp = getResourceStringArray(res, config_mobile_hotspot_provision_app);
+        provisioningApp = getResourceStringArray(res, R.array.config_mobile_hotspot_provision_app);
         provisioningAppNoUi = getProvisioningAppNoUi(res);
         provisioningCheckPeriod = getResourceInteger(res,
-                config_mobile_hotspot_provision_check_period,
+                R.integer.config_mobile_hotspot_provision_check_period,
                 0 /* No periodic re-check */);
 
         configLog.log(toString());
@@ -248,7 +240,7 @@
     }
 
     private static Collection<Integer> getUpstreamIfaceTypes(Resources res, boolean dunRequired) {
-        final int[] ifaceTypes = res.getIntArray(config_tether_upstream_types);
+        final int[] ifaceTypes = res.getIntArray(R.array.config_tether_upstream_types);
         final ArrayList<Integer> upstreamIfaceTypes = new ArrayList<>(ifaceTypes.length);
         for (int i : ifaceTypes) {
             switch (i) {
@@ -298,7 +290,7 @@
     }
 
     private static String[] getLegacyDhcpRanges(Resources res) {
-        final String[] fromResource = getResourceStringArray(res, config_tether_dhcp_range);
+        final String[] fromResource = getResourceStringArray(res, R.array.config_tether_dhcp_range);
         if ((fromResource.length > 0) && (fromResource.length % 2 == 0)) {
             return fromResource;
         }
@@ -307,7 +299,7 @@
 
     private static String getProvisioningAppNoUi(Resources res) {
         try {
-            return res.getString(config_mobile_hotspot_provision_app_no_ui);
+            return res.getString(R.string.config_mobile_hotspot_provision_app_no_ui);
         } catch (Resources.NotFoundException e) {
             return "";
         }
@@ -339,7 +331,7 @@
     }
 
     private boolean getEnableLegacyDhcpServer(final Resources res) {
-        return getResourceBoolean(res, config_tether_enable_legacy_dhcp_server)
+        return getResourceBoolean(res, R.bool.config_tether_enable_legacy_dhcp_server)
                 || getDeviceConfigBoolean(TETHER_ENABLE_LEGACY_DHCP_SERVER);
     }
 
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java
index b16b329..e019c3a 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java
@@ -16,18 +16,15 @@
 
 package com.android.server.connectivity.tethering;
 
+import android.bluetooth.BluetoothAdapter;
 import android.content.Context;
 import android.net.INetd;
-import android.net.INetworkPolicyManager;
-import android.net.INetworkStatsService;
 import android.net.NetworkRequest;
 import android.net.ip.IpServer;
 import android.net.util.SharedLog;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.INetworkManagementService;
 import android.os.Looper;
-import android.os.ServiceManager;
 
 import com.android.internal.util.StateMachine;
 
@@ -97,33 +94,6 @@
     }
 
     /**
-     * Get a reference to INetworkManagementService to registerTetheringStatsProvider from
-     * OffloadController. Note: This should be removed soon by Usage refactor work in R
-     * development cycle.
-     */
-    public INetworkManagementService getINetworkManagementService() {
-        return INetworkManagementService.Stub.asInterface(
-                ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
-    }
-
-    /**
-     *  Get a reference to INetworkStatsService to force update tethering usage.
-     *  Note: This should be removed in R development cycle.
-     */
-    public INetworkStatsService getINetworkStatsService() {
-        return INetworkStatsService.Stub.asInterface(
-                ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
-    }
-
-    /**
-     * Get a reference to INetworkPolicyManager to be used by tethering.
-     */
-    public INetworkPolicyManager getINetworkPolicyManager() {
-        return INetworkPolicyManager.Stub.asInterface(
-                ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
-    }
-
-    /**
      * Get a reference to INetd to be used by tethering.
      */
     public INetd getINetd(Context context) {
@@ -140,4 +110,9 @@
      *  Get Context of TetheringSerice.
      */
     public abstract Context getContext();
+
+    /**
+     * Get a reference to BluetoothAdapter to be used by tethering.
+     */
+    public abstract BluetoothAdapter getBluetoothAdapter();
 }
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java
index d5cdd8a..4dd6830 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java
@@ -22,6 +22,8 @@
 import android.net.RouteInfo;
 import android.net.util.InterfaceSet;
 
+import com.android.net.module.util.NetUtils;
+
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 
@@ -85,7 +87,7 @@
 
     private static String getInterfaceForDestination(LinkProperties lp, InetAddress dst) {
         final RouteInfo ri = (lp != null)
-                ? RouteInfo.selectBestRoute(lp.getAllRoutes(), dst)
+                ? NetUtils.selectBestRoute(lp.getAllRoutes(), dst)
                 : null;
         return (ri != null) ? ri.getInterface() : null;
     }
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java
index e4e4a09..cb7d392 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java
@@ -24,6 +24,7 @@
 import static android.net.dhcp.IDhcpServer.STATUS_UNKNOWN_ERROR;
 
 import android.app.Service;
+import android.bluetooth.BluetoothAdapter;
 import android.content.Context;
 import android.content.Intent;
 import android.net.IIntResultListener;
@@ -42,7 +43,6 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
-import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -363,7 +363,7 @@
                     IBinder connector;
                     try {
                         final long before = System.currentTimeMillis();
-                        while ((connector = ServiceManager.getService(
+                        while ((connector = (IBinder) mContext.getSystemService(
                                 Context.NETWORK_STACK_SERVICE)) == null) {
                             if (System.currentTimeMillis() - before > NETWORKSTACK_TIMEOUT_MS) {
                                 Log.wtf(TAG, "Timeout, fail to get INetworkStackConnector");
@@ -377,6 +377,11 @@
                     }
                     return INetworkStackConnector.Stub.asInterface(connector);
                 }
+
+                @Override
+                public BluetoothAdapter getBluetoothAdapter() {
+                    return BluetoothAdapter.getDefaultAdapter();
+                }
             };
         }
         return mDeps;
diff --git a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index 65a0ac1..1f50b6b 100644
--- a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -52,7 +52,6 @@
 import static org.mockito.Mockito.when;
 
 import android.net.INetd;
-import android.net.INetworkStatsService;
 import android.net.InterfaceConfigurationParcel;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
@@ -99,7 +98,6 @@
     private static final int MAKE_DHCPSERVER_TIMEOUT_MS = 1000;
 
     @Mock private INetd mNetd;
-    @Mock private INetworkStatsService mStatsService;
     @Mock private IpServer.Callback mCallback;
     @Mock private SharedLog mSharedLog;
     @Mock private IDhcpServer mDhcpServer;
@@ -139,13 +137,13 @@
             mInterfaceConfiguration.prefixLength = BLUETOOTH_DHCP_PREFIX_LENGTH;
         }
         mIpServer = new IpServer(
-                IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd, mStatsService,
+                IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd,
                 mCallback, usingLegacyDhcp, mDependencies);
         mIpServer.start();
         // Starting the state machine always puts us in a consistent state and notifies
         // the rest of the world that we've changed from an unknown to available state.
         mLooper.dispatchAll();
-        reset(mNetd, mStatsService, mCallback);
+        reset(mNetd, mCallback);
 
         when(mRaDaemon.start()).thenReturn(true);
     }
@@ -162,7 +160,7 @@
         if (upstreamIface != null) {
             dispatchTetherConnectionChanged(upstreamIface);
         }
-        reset(mNetd, mStatsService, mCallback);
+        reset(mNetd, mCallback);
     }
 
     @Before public void setUp() throws Exception {
@@ -173,13 +171,13 @@
     @Test
     public void startsOutAvailable() {
         mIpServer = new IpServer(IFACE_NAME, mLooper.getLooper(), TETHERING_BLUETOOTH, mSharedLog,
-                mNetd, mStatsService, mCallback, false /* usingLegacyDhcp */, mDependencies);
+                mNetd, mCallback, false /* usingLegacyDhcp */, mDependencies);
         mIpServer.start();
         mLooper.dispatchAll();
         verify(mCallback).updateInterfaceState(
                 mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
         verify(mCallback).updateLinkProperties(eq(mIpServer), any(LinkProperties.class));
-        verifyNoMoreInteractions(mCallback, mNetd, mStatsService);
+        verifyNoMoreInteractions(mCallback, mNetd);
     }
 
     @Test
@@ -198,7 +196,7 @@
             // None of these commands should trigger us to request action from
             // the rest of the system.
             dispatchCommand(command);
-            verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
+            verifyNoMoreInteractions(mNetd, mCallback);
         }
     }
 
@@ -210,7 +208,7 @@
         verify(mCallback).updateInterfaceState(
                 mIpServer, STATE_UNAVAILABLE, TETHER_ERROR_NO_ERROR);
         verify(mCallback).updateLinkProperties(eq(mIpServer), any(LinkProperties.class));
-        verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
+        verifyNoMoreInteractions(mNetd, mCallback);
     }
 
     @Test
@@ -228,7 +226,7 @@
                 mIpServer, STATE_TETHERED, TETHER_ERROR_NO_ERROR);
         inOrder.verify(mCallback).updateLinkProperties(
                 eq(mIpServer), any(LinkProperties.class));
-        verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
+        verifyNoMoreInteractions(mNetd, mCallback);
     }
 
     @Test
@@ -236,7 +234,7 @@
         initTetheredStateMachine(TETHERING_BLUETOOTH, null);
 
         dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED);
-        InOrder inOrder = inOrder(mNetd, mStatsService, mCallback);
+        InOrder inOrder = inOrder(mNetd, mCallback);
         inOrder.verify(mNetd).tetherApplyDnsInterfaces();
         inOrder.verify(mNetd).tetherInterfaceRemove(IFACE_NAME);
         inOrder.verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
@@ -245,7 +243,7 @@
                 mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
         inOrder.verify(mCallback).updateLinkProperties(
                 eq(mIpServer), any(LinkProperties.class));
-        verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
+        verifyNoMoreInteractions(mNetd, mCallback);
     }
 
     @Test
@@ -265,7 +263,7 @@
         inOrder.verify(mCallback).updateLinkProperties(
                 eq(mIpServer), mLinkPropertiesCaptor.capture());
         assertIPv4AddressAndDirectlyConnectedRoute(mLinkPropertiesCaptor.getValue());
-        verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
+        verifyNoMoreInteractions(mNetd, mCallback);
     }
 
     @Test
@@ -285,7 +283,7 @@
         inOrder.verify(mCallback).updateLinkProperties(
                 eq(mIpServer), mLinkPropertiesCaptor.capture());
         assertIPv4AddressAndDirectlyConnectedRoute(mLinkPropertiesCaptor.getValue());
-        verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
+        verifyNoMoreInteractions(mNetd, mCallback);
     }
 
     @Test
@@ -298,7 +296,7 @@
         InOrder inOrder = inOrder(mNetd);
         inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE);
         inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
-        verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
+        verifyNoMoreInteractions(mNetd, mCallback);
     }
 
     @Test
@@ -306,13 +304,12 @@
         initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
 
         dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
-        InOrder inOrder = inOrder(mNetd, mStatsService);
-        inOrder.verify(mStatsService).forceUpdate();
+        InOrder inOrder = inOrder(mNetd);
         inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
         inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
         inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
         inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
-        verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
+        verifyNoMoreInteractions(mNetd, mCallback);
     }
 
     @Test
@@ -322,12 +319,10 @@
         doThrow(RemoteException.class).when(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
 
         dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
-        InOrder inOrder = inOrder(mNetd, mStatsService);
-        inOrder.verify(mStatsService).forceUpdate();
+        InOrder inOrder = inOrder(mNetd);
         inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
         inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
         inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
-        inOrder.verify(mStatsService).forceUpdate();
         inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
         inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE2);
     }
@@ -340,13 +335,11 @@
                 IFACE_NAME, UPSTREAM_IFACE2);
 
         dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
-        InOrder inOrder = inOrder(mNetd, mStatsService);
-        inOrder.verify(mStatsService).forceUpdate();
+        InOrder inOrder = inOrder(mNetd);
         inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
         inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
         inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
         inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
-        inOrder.verify(mStatsService).forceUpdate();
         inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
         inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE2);
     }
@@ -356,8 +349,7 @@
         initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
 
         dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED);
-        InOrder inOrder = inOrder(mNetd, mStatsService, mCallback);
-        inOrder.verify(mStatsService).forceUpdate();
+        InOrder inOrder = inOrder(mNetd, mCallback);
         inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
         inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
         inOrder.verify(mNetd).tetherApplyDnsInterfaces();
@@ -368,7 +360,7 @@
                 mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
         inOrder.verify(mCallback).updateLinkProperties(
                 eq(mIpServer), any(LinkProperties.class));
-        verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
+        verifyNoMoreInteractions(mNetd, mCallback);
     }
 
     @Test
@@ -435,11 +427,11 @@
     public void ignoresDuplicateUpstreamNotifications() throws Exception {
         initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE);
 
-        verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
+        verifyNoMoreInteractions(mNetd, mCallback);
 
         for (int i = 0; i < 5; i++) {
             dispatchTetherConnectionChanged(UPSTREAM_IFACE);
-            verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
+            verifyNoMoreInteractions(mNetd, mCallback);
         }
     }
 
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
index 79bba7f..4f07461 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
@@ -27,7 +27,6 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-import static com.android.networkstack.tethering.R.bool.config_tether_enable_legacy_dhcp_server;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -56,10 +55,10 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.R;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 import com.android.internal.util.test.BroadcastInterceptingContext;
+import com.android.networkstack.tethering.R;
 
 import org.junit.After;
 import org.junit.Before;
@@ -157,16 +156,18 @@
                 eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean()));
 
         when(mResources.getStringArray(R.array.config_tether_dhcp_range))
-            .thenReturn(new String[0]);
+                .thenReturn(new String[0]);
         when(mResources.getStringArray(R.array.config_tether_usb_regexs))
-            .thenReturn(new String[0]);
+                .thenReturn(new String[0]);
         when(mResources.getStringArray(R.array.config_tether_wifi_regexs))
-            .thenReturn(new String[0]);
+                .thenReturn(new String[0]);
         when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs))
-            .thenReturn(new String[0]);
+                .thenReturn(new String[0]);
         when(mResources.getIntArray(R.array.config_tether_upstream_types))
-            .thenReturn(new int[0]);
-        when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(false);
+                .thenReturn(new int[0]);
+        when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
+                false);
+        when(mResources.getString(R.string.config_wifi_tether_enable)).thenReturn("");
         when(mLog.forSubComponent(anyString())).thenReturn(mLog);
 
         mMockContext = new MockContext(mContext);
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/OffloadControllerTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/OffloadControllerTest.java
index 7886ca6..7e62e5a 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/OffloadControllerTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/OffloadControllerTest.java
@@ -16,21 +16,26 @@
 
 package com.android.server.connectivity.tethering;
 
+import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
+import static android.net.NetworkStats.METERED_NO;
+import static android.net.NetworkStats.ROAMING_NO;
 import static android.net.NetworkStats.SET_DEFAULT;
-import static android.net.NetworkStats.STATS_PER_IFACE;
-import static android.net.NetworkStats.STATS_PER_UID;
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
+import static android.net.NetworkStats.UID_TETHERING;
 import static android.net.RouteInfo.RTN_UNICAST;
-import static android.net.TrafficStats.UID_TETHERING;
 import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
 
+import static com.android.server.connectivity.tethering.OffloadController.StatsType.STATS_PER_IFACE;
+import static com.android.server.connectivity.tethering.OffloadController.StatsType.STATS_PER_UID;
 import static com.android.server.connectivity.tethering.OffloadHardwareInterface.ForwardedStats;
 import static com.android.testutils.MiscAssertsKt.assertContainsAll;
 import static com.android.testutils.MiscAssertsKt.assertThrows;
+import static com.android.testutils.NetworkStatsUtilsKt.orderInsensitiveEquals;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.anyObject;
@@ -39,11 +44,14 @@
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
+import android.annotation.NonNull;
+import android.app.usage.NetworkStatsManager;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.net.ITetheringStatsProvider;
@@ -51,10 +59,12 @@
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.NetworkStats;
+import android.net.NetworkStats.Entry;
 import android.net.RouteInfo;
+import android.net.netstats.provider.AbstractNetworkStatsProvider;
+import android.net.netstats.provider.NetworkStatsProviderCallback;
 import android.net.util.SharedLog;
 import android.os.Handler;
-import android.os.INetworkManagementService;
 import android.os.Looper;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
@@ -97,11 +107,13 @@
     @Mock private OffloadHardwareInterface mHardware;
     @Mock private ApplicationInfo mApplicationInfo;
     @Mock private Context mContext;
-    @Mock private INetworkManagementService mNMService;
+    @Mock private NetworkStatsManager mStatsManager;
+    @Mock private NetworkStatsProviderCallback mTetherStatsProviderCb;
     private final ArgumentCaptor<ArrayList> mStringArrayCaptor =
             ArgumentCaptor.forClass(ArrayList.class);
-    private final ArgumentCaptor<ITetheringStatsProvider.Stub> mTetherStatsProviderCaptor =
-            ArgumentCaptor.forClass(ITetheringStatsProvider.Stub.class);
+    private final ArgumentCaptor<OffloadController.OffloadTetheringStatsProvider>
+            mTetherStatsProviderCaptor =
+            ArgumentCaptor.forClass(OffloadController.OffloadTetheringStatsProvider.class);
     private final ArgumentCaptor<OffloadHardwareInterface.ControlCallback> mControlCallbackCaptor =
             ArgumentCaptor.forClass(OffloadHardwareInterface.ControlCallback.class);
     private MockContentResolver mContentResolver;
@@ -114,6 +126,8 @@
         mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
         when(mContext.getContentResolver()).thenReturn(mContentResolver);
         FakeSettingsProvider.clearSettingsProvider();
+        when(mStatsManager.registerNetworkStatsProvider(anyString(), any()))
+                .thenReturn(mTetherStatsProviderCb);
     }
 
     @After public void tearDown() throws Exception {
@@ -139,9 +153,9 @@
 
     private OffloadController makeOffloadController() throws Exception {
         OffloadController offload = new OffloadController(new Handler(Looper.getMainLooper()),
-                mHardware, mContentResolver, mNMService, new SharedLog("test"));
-        verify(mNMService).registerTetheringStatsProvider(
-                mTetherStatsProviderCaptor.capture(), anyString());
+                mHardware, mContentResolver, mStatsManager, new SharedLog("test"));
+        verify(mStatsManager).registerNetworkStatsProvider(anyString(),
+                mTetherStatsProviderCaptor.capture());
         return offload;
     }
 
@@ -384,12 +398,11 @@
         inOrder.verifyNoMoreInteractions();
     }
 
-    private void assertNetworkStats(String iface, ForwardedStats stats, NetworkStats.Entry entry) {
-        assertEquals(iface, entry.iface);
-        assertEquals(stats.rxBytes, entry.rxBytes);
-        assertEquals(stats.txBytes, entry.txBytes);
-        assertEquals(SET_DEFAULT, entry.set);
-        assertEquals(TAG_NONE, entry.tag);
+    private static @NonNull Entry buildTestEntry(@NonNull OffloadController.StatsType how,
+            @NonNull String iface, long rxBytes, long txBytes) {
+        return new Entry(iface, how == STATS_PER_IFACE ? UID_ALL : UID_TETHERING, SET_DEFAULT,
+                TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, rxBytes, 0L,
+                txBytes, 0L, 0L);
     }
 
     @Test
@@ -400,19 +413,16 @@
         final OffloadController offload = makeOffloadController();
         offload.start();
 
+        final OffloadController.OffloadTetheringStatsProvider provider =
+                mTetherStatsProviderCaptor.getValue();
+
         final String ethernetIface = "eth1";
         final String mobileIface = "rmnet_data0";
 
-        ForwardedStats ethernetStats = new ForwardedStats();
-        ethernetStats.rxBytes = 12345;
-        ethernetStats.txBytes = 54321;
-
-        ForwardedStats mobileStats = new ForwardedStats();
-        mobileStats.rxBytes = 999;
-        mobileStats.txBytes = 99999;
-
-        when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(ethernetStats);
-        when(mHardware.getForwardedStats(eq(mobileIface))).thenReturn(mobileStats);
+        when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(
+                new ForwardedStats(12345, 54321));
+        when(mHardware.getForwardedStats(eq(mobileIface))).thenReturn(
+                new ForwardedStats(999, 99999));
 
         InOrder inOrder = inOrder(mHardware);
 
@@ -432,10 +442,35 @@
         // Expect that we fetch stats from the previous upstream.
         inOrder.verify(mHardware, times(1)).getForwardedStats(eq(mobileIface));
 
-        ethernetStats = new ForwardedStats();
-        ethernetStats.rxBytes = 100000;
-        ethernetStats.txBytes = 100000;
-        when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(ethernetStats);
+        // Verify that the fetched stats are stored.
+        final NetworkStats ifaceStats = provider.getTetherStats(STATS_PER_IFACE);
+        final NetworkStats uidStats = provider.getTetherStats(STATS_PER_UID);
+        final NetworkStats expectedIfaceStats = new NetworkStats(0L, 2)
+                .addValues(buildTestEntry(STATS_PER_IFACE, mobileIface, 999, 99999))
+                .addValues(buildTestEntry(STATS_PER_IFACE, ethernetIface, 12345, 54321));
+
+        final NetworkStats expectedUidStats = new NetworkStats(0L, 2)
+                .addValues(buildTestEntry(STATS_PER_UID, mobileIface, 999, 99999))
+                .addValues(buildTestEntry(STATS_PER_UID, ethernetIface, 12345, 54321));
+
+        assertTrue(orderInsensitiveEquals(expectedIfaceStats, ifaceStats));
+        assertTrue(orderInsensitiveEquals(expectedUidStats, uidStats));
+
+        final ArgumentCaptor<NetworkStats> ifaceStatsCaptor = ArgumentCaptor.forClass(
+                NetworkStats.class);
+        final ArgumentCaptor<NetworkStats> uidStatsCaptor = ArgumentCaptor.forClass(
+                NetworkStats.class);
+
+        // Force pushing stats update to verify the stats reported.
+        provider.pushTetherStats();
+        verify(mTetherStatsProviderCb, times(1)).onStatsUpdated(anyInt(),
+                ifaceStatsCaptor.capture(), uidStatsCaptor.capture());
+        assertTrue(orderInsensitiveEquals(expectedIfaceStats, ifaceStatsCaptor.getValue()));
+        assertTrue(orderInsensitiveEquals(expectedUidStats, uidStatsCaptor.getValue()));
+
+
+        when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(
+                new ForwardedStats(100000, 100000));
         offload.setUpstreamLinkProperties(null);
         // Expect that we first clear the HAL's upstream parameters.
         inOrder.verify(mHardware, times(1)).setUpstreamParameters(
@@ -443,37 +478,38 @@
         // Expect that we fetch stats from the previous upstream.
         inOrder.verify(mHardware, times(1)).getForwardedStats(eq(ethernetIface));
 
-        ITetheringStatsProvider provider = mTetherStatsProviderCaptor.getValue();
-        NetworkStats stats = provider.getTetherStats(STATS_PER_IFACE);
-        NetworkStats perUidStats = provider.getTetherStats(STATS_PER_UID);
-        waitForIdle();
         // There is no current upstream, so no stats are fetched.
         inOrder.verify(mHardware, never()).getForwardedStats(any());
         inOrder.verifyNoMoreInteractions();
 
-        assertEquals(2, stats.size());
-        assertEquals(2, perUidStats.size());
+        // Verify that the stored stats is accumulated.
+        final NetworkStats ifaceStatsAccu = provider.getTetherStats(STATS_PER_IFACE);
+        final NetworkStats uidStatsAccu = provider.getTetherStats(STATS_PER_UID);
+        final NetworkStats expectedIfaceStatsAccu = new NetworkStats(0L, 2)
+                .addValues(buildTestEntry(STATS_PER_IFACE, mobileIface, 999, 99999))
+                .addValues(buildTestEntry(STATS_PER_IFACE, ethernetIface, 112345, 154321));
 
-        NetworkStats.Entry entry = null;
-        for (int i = 0; i < stats.size(); i++) {
-            assertEquals(UID_ALL, stats.getValues(i, entry).uid);
-            assertEquals(UID_TETHERING, perUidStats.getValues(i, entry).uid);
-        }
+        final NetworkStats expectedUidStatsAccu = new NetworkStats(0L, 2)
+                .addValues(buildTestEntry(STATS_PER_UID, mobileIface, 999, 99999))
+                .addValues(buildTestEntry(STATS_PER_UID, ethernetIface, 112345, 154321));
 
-        int ethernetPosition = ethernetIface.equals(stats.getValues(0, entry).iface) ? 0 : 1;
-        int mobilePosition = 1 - ethernetPosition;
+        assertTrue(orderInsensitiveEquals(expectedIfaceStatsAccu, ifaceStatsAccu));
+        assertTrue(orderInsensitiveEquals(expectedUidStatsAccu, uidStatsAccu));
 
-        entry = stats.getValues(mobilePosition, entry);
-        assertNetworkStats(mobileIface, mobileStats, entry);
-        entry = perUidStats.getValues(mobilePosition, entry);
-        assertNetworkStats(mobileIface, mobileStats, entry);
+        // Verify that only diff of stats is reported.
+        reset(mTetherStatsProviderCb);
+        provider.pushTetherStats();
+        final NetworkStats expectedIfaceStatsDiff = new NetworkStats(0L, 2)
+                .addValues(buildTestEntry(STATS_PER_IFACE, mobileIface, 0, 0))
+                .addValues(buildTestEntry(STATS_PER_IFACE, ethernetIface, 100000, 100000));
 
-        ethernetStats.rxBytes = 12345 + 100000;
-        ethernetStats.txBytes = 54321 + 100000;
-        entry = stats.getValues(ethernetPosition, entry);
-        assertNetworkStats(ethernetIface, ethernetStats, entry);
-        entry = perUidStats.getValues(ethernetPosition, entry);
-        assertNetworkStats(ethernetIface, ethernetStats, entry);
+        final NetworkStats expectedUidStatsDiff = new NetworkStats(0L, 2)
+                .addValues(buildTestEntry(STATS_PER_UID, mobileIface, 0, 0))
+                .addValues(buildTestEntry(STATS_PER_UID, ethernetIface, 100000, 100000));
+        verify(mTetherStatsProviderCb, times(1)).onStatsUpdated(anyInt(),
+                ifaceStatsCaptor.capture(), uidStatsCaptor.capture());
+        assertTrue(orderInsensitiveEquals(expectedIfaceStatsDiff, ifaceStatsCaptor.getValue()));
+        assertTrue(orderInsensitiveEquals(expectedUidStatsDiff, uidStatsCaptor.getValue()));
     }
 
     @Test
@@ -493,19 +529,19 @@
         lp.setInterfaceName(ethernetIface);
         offload.setUpstreamLinkProperties(lp);
 
-        ITetheringStatsProvider provider = mTetherStatsProviderCaptor.getValue();
+        AbstractNetworkStatsProvider provider = mTetherStatsProviderCaptor.getValue();
         final InOrder inOrder = inOrder(mHardware);
         when(mHardware.setUpstreamParameters(any(), any(), any(), any())).thenReturn(true);
         when(mHardware.setDataLimit(anyString(), anyLong())).thenReturn(true);
 
         // Applying an interface quota to the current upstream immediately sends it to the hardware.
-        provider.setInterfaceQuota(ethernetIface, ethernetLimit);
+        provider.setLimit(ethernetIface, ethernetLimit);
         waitForIdle();
         inOrder.verify(mHardware).setDataLimit(ethernetIface, ethernetLimit);
         inOrder.verifyNoMoreInteractions();
 
         // Applying an interface quota to another upstream does not take any immediate action.
-        provider.setInterfaceQuota(mobileIface, mobileLimit);
+        provider.setLimit(mobileIface, mobileLimit);
         waitForIdle();
         inOrder.verify(mHardware, never()).setDataLimit(anyString(), anyLong());
 
@@ -518,7 +554,7 @@
 
         // Setting a limit of ITetheringStatsProvider.QUOTA_UNLIMITED causes the limit to be set
         // to Long.MAX_VALUE.
-        provider.setInterfaceQuota(mobileIface, ITetheringStatsProvider.QUOTA_UNLIMITED);
+        provider.setLimit(mobileIface, ITetheringStatsProvider.QUOTA_UNLIMITED);
         waitForIdle();
         inOrder.verify(mHardware).setDataLimit(mobileIface, Long.MAX_VALUE);
 
@@ -526,7 +562,7 @@
         when(mHardware.setUpstreamParameters(any(), any(), any(), any())).thenReturn(false);
         lp.setInterfaceName(ethernetIface);
         offload.setUpstreamLinkProperties(lp);
-        provider.setInterfaceQuota(mobileIface, mobileLimit);
+        provider.setLimit(mobileIface, mobileLimit);
         waitForIdle();
         inOrder.verify(mHardware, never()).setDataLimit(anyString(), anyLong());
 
@@ -535,7 +571,7 @@
         when(mHardware.setDataLimit(anyString(), anyLong())).thenReturn(false);
         lp.setInterfaceName(mobileIface);
         offload.setUpstreamLinkProperties(lp);
-        provider.setInterfaceQuota(mobileIface, mobileLimit);
+        provider.setLimit(mobileIface, mobileLimit);
         waitForIdle();
         inOrder.verify(mHardware).getForwardedStats(ethernetIface);
         inOrder.verify(mHardware).stopOffloadControl();
@@ -551,7 +587,7 @@
 
         OffloadHardwareInterface.ControlCallback callback = mControlCallbackCaptor.getValue();
         callback.onStoppedLimitReached();
-        verify(mNMService, times(1)).tetherLimitReached(mTetherStatsProviderCaptor.getValue());
+        verify(mTetherStatsProviderCb, times(1)).onStatsUpdated(anyInt(), any(), any());
     }
 
     @Test
@@ -654,9 +690,10 @@
         // Verify forwarded stats behaviour.
         verify(mHardware, times(1)).getForwardedStats(eq(RMNET0));
         verify(mHardware, times(1)).getForwardedStats(eq(WLAN0));
+        // TODO: verify the exact stats reported.
+        verify(mTetherStatsProviderCb, times(1)).onStatsUpdated(anyInt(), any(), any());
+        verifyNoMoreInteractions(mTetherStatsProviderCb);
         verifyNoMoreInteractions(mHardware);
-        verify(mNMService, times(1)).tetherLimitReached(mTetherStatsProviderCaptor.getValue());
-        verifyNoMoreInteractions(mNMService);
     }
 
     @Test
@@ -719,8 +756,8 @@
         // Verify forwarded stats behaviour.
         verify(mHardware, times(1)).getForwardedStats(eq(RMNET0));
         verify(mHardware, times(1)).getForwardedStats(eq(WLAN0));
-        verify(mNMService, times(1)).tetherLimitReached(mTetherStatsProviderCaptor.getValue());
-        verifyNoMoreInteractions(mNMService);
+        verify(mTetherStatsProviderCb, times(1)).onStatsUpdated(anyInt(), any(), any());
+        verifyNoMoreInteractions(mTetherStatsProviderCb);
 
         // TODO: verify local prefixes and downstreams are also pushed to the HAL.
         verify(mHardware, times(1)).setLocalPrefixes(mStringArrayCaptor.capture());
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
index ef97ad4..3635964 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
@@ -26,13 +26,6 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-import static com.android.internal.R.array.config_mobile_hotspot_provision_app;
-import static com.android.internal.R.array.config_tether_bluetooth_regexs;
-import static com.android.internal.R.array.config_tether_dhcp_range;
-import static com.android.internal.R.array.config_tether_upstream_types;
-import static com.android.internal.R.array.config_tether_usb_regexs;
-import static com.android.internal.R.array.config_tether_wifi_regexs;
-import static com.android.networkstack.tethering.R.bool.config_tether_enable_legacy_dhcp_server;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -51,6 +44,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.util.test.BroadcastInterceptingContext;
+import com.android.networkstack.tethering.R;
 
 import org.junit.After;
 import org.junit.Before;
@@ -120,15 +114,18 @@
                 () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY),
                 eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean()));
 
-        when(mResources.getStringArray(config_tether_dhcp_range)).thenReturn(new String[0]);
-        when(mResources.getStringArray(config_tether_usb_regexs)).thenReturn(new String[0]);
-        when(mResources.getStringArray(config_tether_wifi_regexs))
+        when(mResources.getStringArray(R.array.config_tether_dhcp_range)).thenReturn(
+                new String[0]);
+        when(mResources.getStringArray(R.array.config_tether_usb_regexs)).thenReturn(new String[0]);
+        when(mResources.getStringArray(R.array.config_tether_wifi_regexs))
                 .thenReturn(new String[]{ "test_wlan\\d" });
-        when(mResources.getStringArray(config_tether_bluetooth_regexs)).thenReturn(new String[0]);
-        when(mResources.getIntArray(config_tether_upstream_types)).thenReturn(new int[0]);
-        when(mResources.getStringArray(config_mobile_hotspot_provision_app))
+        when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs)).thenReturn(
+                new String[0]);
+        when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(new int[0]);
+        when(mResources.getStringArray(R.array.config_mobile_hotspot_provision_app))
                 .thenReturn(new String[0]);
-        when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(false);
+        when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
+                false);
         mHasTelephonyManager = true;
         mMockContext = new MockContext(mContext);
         mEnableLegacyDhcpServer = false;
@@ -140,7 +137,7 @@
     }
 
     private TetheringConfiguration getTetheringConfiguration(int... legacyTetherUpstreamTypes) {
-        when(mResources.getIntArray(config_tether_upstream_types)).thenReturn(
+        when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(
                 legacyTetherUpstreamTypes);
         return new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
     }
@@ -224,7 +221,7 @@
 
     @Test
     public void testNoDefinedUpstreamTypesAddsEthernet() {
-        when(mResources.getIntArray(config_tether_upstream_types)).thenReturn(new int[]{});
+        when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(new int[]{});
         when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false);
 
         final TetheringConfiguration cfg = new TetheringConfiguration(
@@ -246,7 +243,7 @@
 
     @Test
     public void testDefinedUpstreamTypesSansEthernetAddsEthernet() {
-        when(mResources.getIntArray(config_tether_upstream_types)).thenReturn(
+        when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(
                 new int[]{TYPE_WIFI, TYPE_MOBILE_HIPRI});
         when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false);
 
@@ -264,7 +261,7 @@
 
     @Test
     public void testDefinedUpstreamTypesWithEthernetDoesNotAddEthernet() {
-        when(mResources.getIntArray(config_tether_upstream_types))
+        when(mResources.getIntArray(R.array.config_tether_upstream_types))
                 .thenReturn(new int[]{TYPE_WIFI, TYPE_ETHERNET, TYPE_MOBILE_HIPRI});
         when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false);
 
@@ -282,7 +279,8 @@
 
     @Test
     public void testNewDhcpServerDisabled() {
-        when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(true);
+        when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
+                true);
         doReturn(false).when(
                 () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY),
                 eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean()));
@@ -291,7 +289,8 @@
                 new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
         assertTrue(enableByRes.enableLegacyDhcpServer);
 
-        when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(false);
+        when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
+                false);
         doReturn(true).when(
                 () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY),
                 eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean()));
@@ -303,7 +302,8 @@
 
     @Test
     public void testNewDhcpServerEnabled() {
-        when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(false);
+        when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
+                false);
         doReturn(false).when(
                 () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY),
                 eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean()));
@@ -329,16 +329,17 @@
 
     private void setUpResourceForSubId() {
         when(mResourcesForSubId.getStringArray(
-                config_tether_dhcp_range)).thenReturn(new String[0]);
+                R.array.config_tether_dhcp_range)).thenReturn(new String[0]);
         when(mResourcesForSubId.getStringArray(
-                config_tether_usb_regexs)).thenReturn(new String[0]);
+                R.array.config_tether_usb_regexs)).thenReturn(new String[0]);
         when(mResourcesForSubId.getStringArray(
-                config_tether_wifi_regexs)).thenReturn(new String[]{ "test_wlan\\d" });
+                R.array.config_tether_wifi_regexs)).thenReturn(new String[]{ "test_wlan\\d" });
         when(mResourcesForSubId.getStringArray(
-                config_tether_bluetooth_regexs)).thenReturn(new String[0]);
-        when(mResourcesForSubId.getIntArray(config_tether_upstream_types)).thenReturn(new int[0]);
+                R.array.config_tether_bluetooth_regexs)).thenReturn(new String[0]);
+        when(mResourcesForSubId.getIntArray(R.array.config_tether_upstream_types)).thenReturn(
+                new int[0]);
         when(mResourcesForSubId.getStringArray(
-                config_mobile_hotspot_provision_app)).thenReturn(PROVISIONING_APP_NAME);
+                R.array.config_mobile_hotspot_provision_app)).thenReturn(PROVISIONING_APP_NAME);
     }
 
 }
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
index 900c671..5910624 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
@@ -19,6 +19,9 @@
 import static android.hardware.usb.UsbManager.USB_CONFIGURED;
 import static android.hardware.usb.UsbManager.USB_CONNECTED;
 import static android.hardware.usb.UsbManager.USB_FUNCTION_RNDIS;
+import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
+import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
+import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
 import static android.net.RouteInfo.RTN_UNICAST;
 import static android.net.TetheringManager.ACTION_TETHER_STATE_CHANGED;
 import static android.net.TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY;
@@ -37,8 +40,6 @@
 import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
-import static com.android.networkstack.tethering.R.bool.config_tether_enable_legacy_dhcp_server;
-
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -53,6 +54,7 @@
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
@@ -60,6 +62,8 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
+import android.app.usage.NetworkStatsManager;
+import android.bluetooth.BluetoothAdapter;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -68,9 +72,8 @@
 import android.content.pm.ApplicationInfo;
 import android.content.res.Resources;
 import android.hardware.usb.UsbManager;
+import android.net.ConnectivityManager;
 import android.net.INetd;
-import android.net.INetworkPolicyManager;
-import android.net.INetworkStatsService;
 import android.net.ITetheringEventCallback;
 import android.net.InetAddresses;
 import android.net.InterfaceConfigurationParcel;
@@ -99,7 +102,6 @@
 import android.net.wifi.p2p.WifiP2pManager;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.INetworkManagementService;
 import android.os.Looper;
 import android.os.PersistableBundle;
 import android.os.RemoteException;
@@ -120,6 +122,7 @@
 import com.android.internal.util.StateMachine;
 import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.networkstack.tethering.R;
 
 import org.junit.After;
 import org.junit.Before;
@@ -133,6 +136,7 @@
 import java.net.Inet6Address;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Vector;
 
 @RunWith(AndroidJUnit4.class)
@@ -151,9 +155,7 @@
 
     @Mock private ApplicationInfo mApplicationInfo;
     @Mock private Context mContext;
-    @Mock private INetworkManagementService mNMService;
-    @Mock private INetworkStatsService mStatsService;
-    @Mock private INetworkPolicyManager mPolicyManager;
+    @Mock private NetworkStatsManager mStatsManager;
     @Mock private OffloadHardwareInterface mOffloadHardwareInterface;
     @Mock private Resources mResources;
     @Mock private TelephonyManager mTelephonyManager;
@@ -167,6 +169,7 @@
     @Mock private INetd mNetd;
     @Mock private UserManager mUserManager;
     @Mock private NetworkRequest mNetworkRequest;
+    @Mock private ConnectivityManager mCm;
 
     private final MockIpServerDependencies mIpServerDependencies =
             spy(new MockIpServerDependencies());
@@ -217,6 +220,8 @@
             if (Context.USB_SERVICE.equals(name)) return mUsbManager;
             if (Context.TELEPHONY_SERVICE.equals(name)) return mTelephonyManager;
             if (Context.USER_SERVICE.equals(name)) return mUserManager;
+            if (Context.NETWORK_STATS_SERVICE.equals(name)) return mStatsManager;
+            if (Context.CONNECTIVITY_SERVICE.equals(name)) return mCm;
             return super.getSystemService(name);
         }
 
@@ -334,21 +339,6 @@
         }
 
         @Override
-        public INetworkManagementService getINetworkManagementService() {
-            return mNMService;
-        }
-
-        @Override
-        public INetworkStatsService getINetworkStatsService() {
-            return mStatsService;
-        }
-
-        @Override
-        public INetworkPolicyManager getINetworkPolicyManager() {
-            return mPolicyManager;
-        }
-
-        @Override
         public INetd getINetd(Context context) {
             return mNetd;
         }
@@ -362,6 +352,12 @@
         public Context getContext() {
             return mServiceContext;
         }
+
+        @Override
+        public BluetoothAdapter getBluetoothAdapter() {
+            // TODO: add test for bluetooth tethering.
+            return null;
+        }
     }
 
     private static UpstreamNetworkState buildMobileUpstreamState(boolean withIPv4,
@@ -420,24 +416,24 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        when(mResources.getStringArray(com.android.internal.R.array.config_tether_dhcp_range))
+        when(mResources.getStringArray(R.array.config_tether_dhcp_range))
                 .thenReturn(new String[0]);
-        when(mResources.getStringArray(com.android.internal.R.array.config_tether_usb_regexs))
+        when(mResources.getStringArray(R.array.config_tether_usb_regexs))
                 .thenReturn(new String[] { "test_rndis\\d" });
-        when(mResources.getStringArray(com.android.internal.R.array.config_tether_wifi_regexs))
+        when(mResources.getStringArray(R.array.config_tether_wifi_regexs))
                 .thenReturn(new String[]{ "test_wlan\\d" });
-        when(mResources.getStringArray(com.android.internal.R.array.config_tether_wifi_p2p_regexs))
+        when(mResources.getStringArray(R.array.config_tether_wifi_p2p_regexs))
                 .thenReturn(new String[]{ "test_p2p-p2p\\d-.*" });
-        when(mResources.getStringArray(com.android.internal.R.array.config_tether_bluetooth_regexs))
+        when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs))
                 .thenReturn(new String[0]);
-        when(mResources.getIntArray(com.android.internal.R.array.config_tether_upstream_types))
-                .thenReturn(new int[0]);
-        when(mResources.getBoolean(com.android.internal.R.bool.config_tether_upstream_automatic))
-                .thenReturn(false);
-        when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(false);
+        when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(new int[0]);
+        when(mResources.getBoolean(R.bool.config_tether_upstream_automatic)).thenReturn(false);
+        when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
+                false);
         when(mNetd.interfaceGetList())
                 .thenReturn(new String[] {
                         TEST_MOBILE_IFNAME, TEST_WLAN_IFNAME, TEST_USB_IFNAME, TEST_P2P_IFNAME});
+        when(mResources.getString(R.string.config_wifi_tether_enable)).thenReturn("");
         mInterfaceConfiguration = new InterfaceConfigurationParcel();
         mInterfaceConfiguration.flags = new String[0];
         when(mRouterAdvertisementDaemon.start())
@@ -457,7 +453,7 @@
         mServiceContext.registerReceiver(mBroadcastReceiver,
                 new IntentFilter(ACTION_TETHER_STATE_CHANGED));
         mTethering = makeTethering();
-        verify(mNMService).registerTetheringStatsProvider(any(), anyString());
+        verify(mStatsManager, times(1)).registerNetworkStatsProvider(anyString(), any());
         verify(mNetd).registerUnsolicitedEventListener(any());
         final ArgumentCaptor<PhoneStateListener> phoneListenerCaptor =
                 ArgumentCaptor.forClass(PhoneStateListener.class);
@@ -700,7 +696,8 @@
 
     @Test
     public void workingMobileUsbTethering_IPv4LegacyDhcp() {
-        when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(true);
+        when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
+                true);
         sendConfigurationChanged();
         final UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState();
         runUsbTethering(upstreamState);
@@ -788,8 +785,7 @@
 
     @Test
     public void configTetherUpstreamAutomaticIgnoresConfigTetherUpstreamTypes() throws Exception {
-        when(mResources.getBoolean(com.android.internal.R.bool.config_tether_upstream_automatic))
-                .thenReturn(true);
+        when(mResources.getBoolean(R.bool.config_tether_upstream_automatic)).thenReturn(true);
         sendConfigurationChanged();
 
         // Setup IPv6
@@ -1317,7 +1313,7 @@
     private void workingWifiP2pGroupOwnerLegacyMode(
             boolean emulateInterfaceStatusChanged) throws Exception {
         // change to legacy mode and update tethering information by chaning SIM
-        when(mResources.getStringArray(com.android.internal.R.array.config_tether_wifi_p2p_regexs))
+        when(mResources.getStringArray(R.array.config_tether_wifi_p2p_regexs))
                 .thenReturn(new String[]{});
         final int fakeSubId = 1234;
         mPhoneStateListener.onActiveDataSubscriptionIdChanged(fakeSubId);
@@ -1355,6 +1351,50 @@
         workingWifiP2pGroupClient(false);
     }
 
+    private void setDataSaverEnabled(boolean enabled) {
+        final Intent intent = new Intent(ACTION_RESTRICT_BACKGROUND_CHANGED);
+        mServiceContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+
+        final int status = enabled ? RESTRICT_BACKGROUND_STATUS_ENABLED
+                : RESTRICT_BACKGROUND_STATUS_DISABLED;
+        when(mCm.getRestrictBackgroundStatus()).thenReturn(status);
+        mLooper.dispatchAll();
+    }
+
+    @Test
+    public void testDataSaverChanged() {
+        // Start Tethering.
+        final UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState();
+        runUsbTethering(upstreamState);
+        assertContains(Arrays.asList(mTethering.getTetheredIfaces()), TEST_USB_IFNAME);
+        // Data saver is ON.
+        setDataSaverEnabled(true);
+        // Verify that tethering should be disabled.
+        verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_NONE);
+        mTethering.interfaceRemoved(TEST_USB_IFNAME);
+        mLooper.dispatchAll();
+        assertEquals(mTethering.getTetheredIfaces(), new String[0]);
+        reset(mUsbManager);
+
+        runUsbTethering(upstreamState);
+        // Verify that user can start tethering again without turning OFF data saver.
+        assertContains(Arrays.asList(mTethering.getTetheredIfaces()), TEST_USB_IFNAME);
+
+        // If data saver is keep ON with change event, tethering should not be OFF this time.
+        setDataSaverEnabled(true);
+        verify(mUsbManager, times(0)).setCurrentFunctions(UsbManager.FUNCTION_NONE);
+        assertContains(Arrays.asList(mTethering.getTetheredIfaces()), TEST_USB_IFNAME);
+
+        // If data saver is turned OFF, it should not change tethering.
+        setDataSaverEnabled(false);
+        verify(mUsbManager, times(0)).setCurrentFunctions(UsbManager.FUNCTION_NONE);
+        assertContains(Arrays.asList(mTethering.getTetheredIfaces()), TEST_USB_IFNAME);
+    }
+
+    private static <T> void assertContains(Collection<T> collection, T element) {
+        assertTrue(element + " not found in " + collection, collection.contains(element));
+    }
+
     // TODO: Test that a request for hotspot mode doesn't interfere with an
     // already operating tethering mode interface.
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 5eaa80a..c3965c4 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -36,8 +36,12 @@
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.graphics.Rect;
 import android.graphics.Region;
 import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerGlobal;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -54,6 +58,7 @@
 import android.view.Display;
 import android.view.KeyEvent;
 import android.view.MagnificationSpec;
+import android.view.SurfaceControl;
 import android.view.View;
 import android.view.WindowInfo;
 import android.view.accessibility.AccessibilityCache;
@@ -90,6 +95,7 @@
     private static final String LOG_TAG = "AbstractAccessibilityServiceConnection";
     private static final int WAIT_WINDOWS_TIMEOUT_MILLIS = 5000;
 
+    protected static final String TAKE_SCREENSHOT = "takeScreenshot";
     protected final Context mContext;
     protected final SystemSupport mSystemSupport;
     protected final WindowManagerInternal mWindowManagerService;
@@ -934,6 +940,54 @@
         mInvocationHandler.setSoftKeyboardCallbackEnabled(enabled);
     }
 
+    @Nullable
+    @Override
+    public Bitmap takeScreenshot(int displayId) {
+        synchronized (mLock) {
+            if (!hasRightsToCurrentUserLocked()) {
+                return null;
+            }
+
+            if (!mSecurityPolicy.canTakeScreenshotLocked(this)) {
+                return null;
+            }
+        }
+
+        if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
+            return null;
+        }
+
+        final Display display = DisplayManagerGlobal.getInstance()
+                .getRealDisplay(displayId);
+        if (display == null) {
+            return null;
+        }
+        final Point displaySize = new Point();
+        display.getRealSize(displaySize);
+
+        final int rotation = display.getRotation();
+        Bitmap screenShot = null;
+
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            final Rect crop = new Rect(0, 0, displaySize.x, displaySize.y);
+            // TODO (b/145893483): calling new API with the display as a parameter
+            // when surface control supported.
+            screenShot = SurfaceControl.screenshot(crop, displaySize.x, displaySize.y,
+                    rotation);
+            if (screenShot != null) {
+                // Optimization for telling the bitmap that all of the pixels are known to be
+                // opaque (false). This is meant as a drawing hint, as in some cases a bitmap
+                // that is known to be opaque can take a faster drawing case than one that may
+                // have non-opaque per-pixel alpha values.
+                screenShot.setHasAlpha(false);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+        return screenShot;
+    }
+
     @Override
     public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
@@ -1018,6 +1072,20 @@
         }
     }
 
+    /**
+     * Gets windowId of given token.
+     *
+     * @param token The token
+     * @return window id
+     */
+    @Override
+    public int getWindowIdForLeashToken(@NonNull IBinder token) {
+        synchronized (mLock) {
+            // TODO: Add a method to lookup window ID by given leash token.
+            return -1;
+        }
+    }
+
     public void resetLocked() {
         mSystemSupport.getKeyEventDispatcher().flush(this);
         try {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 58d3489..36a9e0eb 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -411,6 +411,7 @@
                     if (reboundAService) {
                         onUserStateChangedLocked(userState);
                     }
+                    migrateAccessibilityButtonSettingsIfNecessaryLocked(userState, packageName);
                 }
             }
 
@@ -811,9 +812,7 @@
 
             userState.setTouchExplorationEnabledLocked(touchExplorationEnabled);
             userState.setDisplayMagnificationEnabledLocked(false);
-            userState.setNavBarMagnificationEnabledLocked(false);
             userState.disableShortcutMagnificationLocked();
-
             userState.setAutoclickEnabledLocked(false);
             userState.mEnabledServices.clear();
             userState.mEnabledServices.add(service);
@@ -853,17 +852,20 @@
      * navigation area has been clicked.
      *
      * @param displayId The logical display id.
+     * @param targetName The flattened {@link ComponentName} string or the class name of a system
+     *        class implementing a supported accessibility feature, or {@code null} if there's no
+     *        specified target.
      */
     @Override
-    public void notifyAccessibilityButtonClicked(int displayId) {
+    public void notifyAccessibilityButtonClicked(int displayId, String targetName) {
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
                 != PackageManager.PERMISSION_GRANTED) {
             throw new SecurityException("Caller does not hold permission "
                     + android.Manifest.permission.STATUS_BAR_SERVICE);
         }
-        synchronized (mLock) {
-            notifyAccessibilityButtonClickedLocked(displayId);
-        }
+        mMainHandler.sendMessage(obtainMessage(
+                AccessibilityManagerService::performAccessibilityShortcutInternal, this,
+                displayId, ACCESSIBILITY_BUTTON, targetName));
     }
 
     /**
@@ -1023,6 +1025,7 @@
             // the state since the context in which the current user
             // state was used has changed since it was inactive.
             onUserStateChangedLocked(userState);
+            migrateAccessibilityButtonSettingsIfNecessaryLocked(userState, null);
 
             if (announceNewUser) {
                 // Schedule announcement of the current user if needed.
@@ -1132,66 +1135,6 @@
         }
     }
 
-    // TODO(a11y shortcut): Remove this function and Use #performAccessibilityShortcutInternal(
-    //  ACCESSIBILITY_BUTTON) instead, after the new Settings shortcut Ui merged.
-    private void notifyAccessibilityButtonClickedLocked(int displayId) {
-        final AccessibilityUserState state = getCurrentUserStateLocked();
-
-        int potentialTargets = state.isNavBarMagnificationEnabledLocked() ? 1 : 0;
-        for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
-            final AccessibilityServiceConnection service = state.mBoundServices.get(i);
-            if (service.mRequestAccessibilityButton) {
-                potentialTargets++;
-            }
-        }
-
-        if (potentialTargets == 0) {
-            return;
-        }
-        if (potentialTargets == 1) {
-            if (state.isNavBarMagnificationEnabledLocked()) {
-                mMainHandler.sendMessage(obtainMessage(
-                        AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this,
-                        displayId));
-                return;
-            } else {
-                for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
-                    final AccessibilityServiceConnection service = state.mBoundServices.get(i);
-                    if (service.mRequestAccessibilityButton) {
-                        service.notifyAccessibilityButtonClickedLocked(displayId);
-                        return;
-                    }
-                }
-            }
-        } else {
-            if (state.getServiceAssignedToAccessibilityButtonLocked() == null
-                    && !state.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
-                mMainHandler.sendMessage(obtainMessage(
-                        AccessibilityManagerService::showAccessibilityTargetsSelection, this,
-                        displayId, ACCESSIBILITY_BUTTON));
-            } else if (state.isNavBarMagnificationEnabledLocked()
-                    && state.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
-                mMainHandler.sendMessage(obtainMessage(
-                        AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this,
-                        displayId));
-                return;
-            } else {
-                for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
-                    final AccessibilityServiceConnection service = state.mBoundServices.get(i);
-                    if (service.mRequestAccessibilityButton && (service.mComponentName.equals(
-                            state.getServiceAssignedToAccessibilityButtonLocked()))) {
-                        service.notifyAccessibilityButtonClickedLocked(displayId);
-                        return;
-                    }
-                }
-            }
-            // The user may have turned off the assigned service or feature
-            mMainHandler.sendMessage(obtainMessage(
-                    AccessibilityManagerService::showAccessibilityTargetsSelection, this,
-                    displayId, ACCESSIBILITY_BUTTON));
-        }
-    }
-
     private void sendAccessibilityButtonToInputFilter(int displayId) {
         synchronized (mLock) {
             if (mHasInputFilter && mInputFilter != null) {
@@ -1635,8 +1578,7 @@
             if (userState.isDisplayMagnificationEnabledLocked()) {
                 flags |= AccessibilityInputFilter.FLAG_FEATURE_SCREEN_MAGNIFIER;
             }
-            if (userState.isNavBarMagnificationEnabledLocked()
-                    || userState.isShortcutKeyMagnificationEnabledLocked()) {
+            if (userState.isShortcutMagnificationEnabledLocked()) {
                 flags |= AccessibilityInputFilter.FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER;
             }
             if (userHasMagnificationServicesLocked(userState)) {
@@ -1886,14 +1828,8 @@
                 mContext.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
                 0, userState.mUserId) == 1;
-        final boolean navBarMagnificationEnabled = Settings.Secure.getIntForUser(
-                mContext.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
-                0, userState.mUserId) == 1;
-        if ((displayMagnificationEnabled != userState.isDisplayMagnificationEnabledLocked())
-                || (navBarMagnificationEnabled != userState.isNavBarMagnificationEnabledLocked())) {
+        if ((displayMagnificationEnabled != userState.isDisplayMagnificationEnabledLocked())) {
             userState.setDisplayMagnificationEnabledLocked(displayMagnificationEnabled);
-            userState.setNavBarMagnificationEnabledLocked(navBarMagnificationEnabled);
             return true;
         }
         return false;
@@ -2088,8 +2024,7 @@
         // displays in one display. It's not a real display and there's no input events for it.
         final ArrayList<Display> displays = getValidDisplayList();
         if (userState.isDisplayMagnificationEnabledLocked()
-                || userState.isNavBarMagnificationEnabledLocked()
-                || userState.isShortcutKeyMagnificationEnabledLocked()) {
+                || userState.isShortcutMagnificationEnabledLocked()) {
             for (int i = 0; i < displays.size(); i++) {
                 final Display display = displays.get(i);
                 getMagnificationController().register(display.getDisplayId());
@@ -2208,6 +2143,85 @@
         scheduleNotifyClientsOfServicesStateChangeLocked(userState);
     }
 
+    /**
+     * 1) Check if the service assigned to accessibility button target sdk version > Q.
+     *    If it isn't, remove it from the list and associated setting.
+     *    (It happens when an accessibility service package is downgraded.)
+     * 2) Check if an enabled service targeting sdk version > Q and requesting a11y button is
+     *    assigned to a shortcut. If it isn't, assigns it to the accessibility button.
+     *    (It happens when an enabled accessibility service package is upgraded.)
+     *
+     * @param packageName The package name to check, or {@code null} to check all services.
+     */
+    private void migrateAccessibilityButtonSettingsIfNecessaryLocked(
+            AccessibilityUserState userState, @Nullable String packageName) {
+        final Set<String> buttonTargets =
+                userState.getShortcutTargetsLocked(ACCESSIBILITY_BUTTON);
+        int lastSize = buttonTargets.size();
+        buttonTargets.removeIf(name -> {
+            if (packageName != null && name != null && !name.contains(packageName)) {
+                return false;
+            }
+            final ComponentName componentName = ComponentName.unflattenFromString(name);
+            if (componentName == null) {
+                return false;
+            }
+            final AccessibilityServiceInfo serviceInfo =
+                    userState.getInstalledServiceInfoLocked(componentName);
+            if (serviceInfo == null) {
+                return false;
+            }
+            if (serviceInfo.getResolveInfo().serviceInfo.applicationInfo
+                    .targetSdkVersion > Build.VERSION_CODES.Q) {
+                return false;
+            }
+            // A11y services targeting sdk version <= Q should not be in the list.
+            return true;
+        });
+        boolean changed = (lastSize != buttonTargets.size());
+        lastSize = buttonTargets.size();
+
+        final Set<String> shortcutKeyTargets =
+                userState.getShortcutTargetsLocked(ACCESSIBILITY_SHORTCUT_KEY);
+        userState.mEnabledServices.forEach(componentName -> {
+            if (packageName != null && componentName != null
+                    && !packageName.equals(componentName.getPackageName())) {
+                return;
+            }
+            final AccessibilityServiceInfo serviceInfo =
+                    userState.getInstalledServiceInfoLocked(componentName);
+            if (serviceInfo == null) {
+                return;
+            }
+            final boolean requestA11yButton = (serviceInfo.flags
+                    & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
+            if (!(serviceInfo.getResolveInfo().serviceInfo.applicationInfo
+                    .targetSdkVersion > Build.VERSION_CODES.Q && requestA11yButton)) {
+                return;
+            }
+            final String serviceName = serviceInfo.getComponentName().flattenToString();
+            if (TextUtils.isEmpty(serviceName)) {
+                return;
+            }
+            if (shortcutKeyTargets.contains(serviceName) || buttonTargets.contains(serviceName)) {
+                return;
+            }
+            // For enabled a11y services targeting sdk version > Q and requesting a11y button should
+            // be assigned to a shortcut.
+            buttonTargets.add(serviceName);
+        });
+        changed |= (lastSize != buttonTargets.size());
+        if (!changed) {
+            return;
+        }
+
+        // Update setting key with new value.
+        persistColonDelimitedSetToSettingLocked(
+                Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
+                userState.mUserId, buttonTargets, str -> str);
+        scheduleNotifyClientsOfServicesStateChangeLocked(userState);
+    }
+
     private void updateRecommendedUiTimeoutLocked(AccessibilityUserState userState) {
         int newNonInteractiveUiTimeout = userState.getUserNonInteractiveUiTimeoutLocked();
         int newInteractiveUiTimeout = userState.getUserInteractiveUiTimeoutLocked();
@@ -2269,9 +2283,13 @@
      * AIDL-exposed method to be called when the accessibility shortcut key is enabled. Requires
      * permission to write secure settings, since someone with that permission can enable
      * accessibility services themselves.
+     *
+     * @param targetName The flattened {@link ComponentName} string or the class name of a system
+     *        class implementing a supported accessibility feature, or {@code null} if there's no
+     *        specified target.
      */
     @Override
-    public void performAccessibilityShortcut() {
+    public void performAccessibilityShortcut(String targetName) {
         if ((UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID)
                 && (mContext.checkCallingPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
                 != PackageManager.PERMISSION_GRANTED)) {
@@ -2280,7 +2298,7 @@
         }
         mMainHandler.sendMessage(obtainMessage(
                 AccessibilityManagerService::performAccessibilityShortcutInternal, this,
-                Display.DEFAULT_DISPLAY, ACCESSIBILITY_SHORTCUT_KEY));
+                Display.DEFAULT_DISPLAY, ACCESSIBILITY_SHORTCUT_KEY, targetName));
     }
 
     /**
@@ -2288,20 +2306,31 @@
      *
      * @param shortcutType The shortcut type.
      * @param displayId The display id of the accessibility button.
+     * @param targetName The flattened {@link ComponentName} string or the class name of a system
+     *        class implementing a supported accessibility feature, or {@code null} if there's no
+     *        specified target.
      */
     private void performAccessibilityShortcutInternal(int displayId,
-            @ShortcutType int shortcutType) {
+            @ShortcutType int shortcutType, @Nullable String targetName) {
         final List<String> shortcutTargets = getAccessibilityShortcutTargetsInternal(shortcutType);
         if (shortcutTargets.isEmpty()) {
             Slog.d(LOG_TAG, "No target to perform shortcut, shortcutType=" + shortcutType);
             return;
         }
-        // In case there are many targets assigned to the given shortcut.
-        if (shortcutTargets.size() > 1) {
-            showAccessibilityTargetsSelection(displayId, shortcutType);
-            return;
+        // In case the caller specified a target name
+        if (targetName != null) {
+            if (!shortcutTargets.contains(targetName)) {
+                Slog.d(LOG_TAG, "Perform shortcut failed, invalid target name:" + targetName);
+                return;
+            }
+        } else {
+            // In case there are many targets assigned to the given shortcut.
+            if (shortcutTargets.size() > 1) {
+                showAccessibilityTargetsSelection(displayId, shortcutType);
+                return;
+            }
+            targetName = shortcutTargets.get(0);
         }
-        final String targetName = shortcutTargets.get(0);
         // In case user assigned magnification to the given shortcut.
         if (targetName.equals(MAGNIFICATION_CONTROLLER_NAME)) {
             sendAccessibilityButtonToInputFilter(displayId);
@@ -2844,11 +2873,6 @@
         private final Uri mDisplayMagnificationEnabledUri = Settings.Secure.getUriFor(
                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED);
 
-        // TODO(a11y shortcut): Remove this setting key, and have a migrate function in
-        //  Setting provider after new shortcut UI merged.
-        private final Uri mNavBarMagnificationEnabledUri = Settings.Secure.getUriFor(
-                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);
-
         private final Uri mAutoclickEnabledUri = Settings.Secure.getUriFor(
                 Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED);
 
@@ -2888,8 +2912,6 @@
                     false, this, UserHandle.USER_ALL);
             contentResolver.registerContentObserver(mDisplayMagnificationEnabledUri,
                     false, this, UserHandle.USER_ALL);
-            contentResolver.registerContentObserver(mNavBarMagnificationEnabledUri,
-                    false, this, UserHandle.USER_ALL);
             contentResolver.registerContentObserver(mAutoclickEnabledUri,
                     false, this, UserHandle.USER_ALL);
             contentResolver.registerContentObserver(mEnabledAccessibilityServicesUri,
@@ -2924,8 +2946,7 @@
                     if (readTouchExplorationEnabledSettingLocked(userState)) {
                         onUserStateChangedLocked(userState);
                     }
-                } else if (mDisplayMagnificationEnabledUri.equals(uri)
-                        || mNavBarMagnificationEnabledUri.equals(uri)) {
+                } else if (mDisplayMagnificationEnabledUri.equals(uri)) {
                     if (readMagnificationEnabledSettingsLocked(userState)) {
                         onUserStateChangedLocked(userState);
                     }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
index 2032109..7dbec7c 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
@@ -326,6 +326,19 @@
     }
 
     /**
+     * Checks if a service can take screenshot.
+     *
+     * @param service The service requesting access
+     *
+     * @return Whether ot not the service may take screenshot
+     */
+    public boolean canTakeScreenshotLocked(
+            @NonNull AbstractAccessibilityServiceConnection service) {
+        return (service.getCapabilities()
+                & AccessibilityServiceInfo.CAPABILITY_CAN_TAKE_SCREENSHOT) != 0;
+    }
+
+    /**
      * Returns the parent userId of the profile according to the specified userId.
      *
      * @param userId The userId to check
@@ -426,6 +439,7 @@
                 return false;
             }
         }
+        // TODO: Check parent windowId if the giving windowId is from embedded view hierarchy.
         if (windowId == mAccessibilityWindowManager.getActiveWindowId(userId)) {
             return true;
         }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index 37ac3ec..25911a7 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -16,6 +16,8 @@
 
 package com.android.server.accessibility;
 
+import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT;
+
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
 import android.Manifest;
@@ -25,16 +27,20 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ParceledListSlice;
+import android.graphics.Bitmap;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Process;
+import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Slog;
 import android.view.Display;
 
+import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
@@ -313,48 +319,16 @@
         }
     }
 
-    // TODO(a11y shortcut): Refactoring the logic here, after the new Settings shortcut Ui merged.
     public boolean isAccessibilityButtonAvailableLocked(AccessibilityUserState userState) {
         // If the service does not request the accessibility button, it isn't available
         if (!mRequestAccessibilityButton) {
             return false;
         }
-
         // If the accessibility button isn't currently shown, it cannot be available to services
         if (!mSystemSupport.isAccessibilityButtonShown()) {
             return false;
         }
-
-        // If magnification is on and assigned to the accessibility button, services cannot be
-        if (userState.isNavBarMagnificationEnabledLocked()
-                && userState.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
-            return false;
-        }
-
-        int requestingServices = 0;
-        for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) {
-            final AccessibilityServiceConnection service = userState.mBoundServices.get(i);
-            if (service.mRequestAccessibilityButton) {
-                requestingServices++;
-            }
-        }
-
-        if (requestingServices == 1) {
-            // If only a single service is requesting, it must be this service, and the
-            // accessibility button is available to it
-            return true;
-        } else {
-            // With more than one active service, we derive the target from the user's settings
-            if (userState.getServiceAssignedToAccessibilityButtonLocked() == null) {
-                // If the user has not made an assignment, we treat the button as available to
-                // all services until the user interacts with the button to make an assignment
-                return true;
-            } else {
-                // If an assignment was made, it defines availability
-                return mComponentName.equals(
-                        userState.getServiceAssignedToAccessibilityButtonLocked());
-            }
-        }
+        return true;
     }
 
     @Override
@@ -419,4 +393,15 @@
             }
         }
     }
+
+    @Override
+    public void takeScreenshotWithCallback(int displayId, RemoteCallback callback) {
+        mMainHandler.post(PooledLambda.obtainRunnable((nonArg) -> {
+            final Bitmap screenshot = super.takeScreenshot(displayId);
+            // Send back the result.
+            final Bundle payload = new Bundle();
+            payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT, screenshot);
+            callback.sendResult(payload);
+        }, null).recycleOnUse());
+    }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index a163f74..ebe2af6 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -100,7 +100,6 @@
     private boolean mIsAutoclickEnabled;
     private boolean mIsDisplayMagnificationEnabled;
     private boolean mIsFilterKeyEventsEnabled;
-    private boolean mIsNavBarMagnificationEnabled;
     private boolean mIsPerformGesturesEnabled;
     private boolean mIsTextHighContrastEnabled;
     private boolean mIsTouchExplorationEnabled;
@@ -153,7 +152,6 @@
         mAccessibilityButtonTargets.clear();
         mIsTouchExplorationEnabled = false;
         mIsDisplayMagnificationEnabled = false;
-        mIsNavBarMagnificationEnabled = false;
         mIsAutoclickEnabled = false;
         mUserNonInteractiveUiTimeout = 0;
         mUserInteractiveUiTimeout = 0;
@@ -435,8 +433,6 @@
         pw.append(", touchExplorationEnabled=").append(String.valueOf(mIsTouchExplorationEnabled));
         pw.append(", displayMagnificationEnabled=").append(String.valueOf(
                 mIsDisplayMagnificationEnabled));
-        pw.append(", navBarMagnificationEnabled=").append(String.valueOf(
-                mIsNavBarMagnificationEnabled));
         pw.append(", autoclickEnabled=").append(String.valueOf(mIsAutoclickEnabled));
         pw.append(", nonInteractiveUiTimeout=").append(String.valueOf(mNonInteractiveUiTimeout));
         pw.append(", interactiveUiTimeout=").append(String.valueOf(mInteractiveUiTimeout));
@@ -553,8 +549,12 @@
         mLastSentClientState = state;
     }
 
-    public boolean isShortcutKeyMagnificationEnabledLocked() {
-        return mAccessibilityShortcutKeyTargets.contains(MAGNIFICATION_CONTROLLER_NAME);
+    /**
+     * Returns true if navibar magnification or shortcut key magnification is enabled.
+     */
+    public boolean isShortcutMagnificationEnabledLocked() {
+        return mAccessibilityShortcutKeyTargets.contains(MAGNIFICATION_CONTROLLER_NAME)
+                || mAccessibilityButtonTargets.contains(MAGNIFICATION_CONTROLLER_NAME);
     }
 
     /**
@@ -690,28 +690,4 @@
     public void setUserNonInteractiveUiTimeoutLocked(int timeout) {
         mUserNonInteractiveUiTimeout = timeout;
     }
-
-    // TODO(a11y shortcut): These functions aren't necessary, after the new Settings shortcut Ui
-    //  is merged.
-    boolean isNavBarMagnificationEnabledLocked() {
-        return mIsNavBarMagnificationEnabled;
-    }
-
-    void setNavBarMagnificationEnabledLocked(boolean enabled) {
-        mIsNavBarMagnificationEnabled = enabled;
-    }
-
-    boolean isNavBarMagnificationAssignedToAccessibilityButtonLocked() {
-        return mAccessibilityButtonTargets.contains(MAGNIFICATION_CONTROLLER_NAME);
-    }
-
-    ComponentName getServiceAssignedToAccessibilityButtonLocked() {
-        final String targetName = mAccessibilityButtonTargets.isEmpty() ? null
-                : mAccessibilityButtonTargets.valueAt(0);
-        if (targetName == null) {
-            return null;
-        }
-        return ComponentName.unflattenFromString(targetName);
-    }
-    // TODO(a11y shortcut): End
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
index 7a8a112..5d9af26 100644
--- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
@@ -25,6 +25,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.IBinder.DeathRecipient;
+import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.util.Slog;
 import android.view.Display;
@@ -325,5 +326,8 @@
 
         @Override
         public void onFingerprintGesture(int gesture) {}
+
+        @Override
+        public void takeScreenshotWithCallback(int displayId, RemoteCallback callback) {}
     }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java
index 2891c6c..4c9e590 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java
@@ -82,7 +82,7 @@
             mCurrentTaps++;
             if (mCurrentTaps == mTargetTaps) {
                 // Done.
-                completeAfterTapTimeout(event, rawEvent, policyFlags);
+                completeGesture(event, rawEvent, policyFlags);
                 return;
             }
             // Needs more taps.
diff --git a/services/api/current.txt b/services/api/current.txt
index d802177..18e38b1 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -1 +1,31 @@
 // Signature format: 2.0
+package com.android.server {
+
+  public abstract class SystemService {
+    ctor public SystemService(@NonNull android.content.Context);
+    method @NonNull public final android.content.Context getContext();
+    method public boolean isSupportedUser(@NonNull com.android.server.SystemService.TargetUser);
+    method public void onBootPhase(int);
+    method public void onCleanupUser(@NonNull com.android.server.SystemService.TargetUser);
+    method public abstract void onStart();
+    method public void onStartUser(@NonNull com.android.server.SystemService.TargetUser);
+    method public void onStopUser(@NonNull com.android.server.SystemService.TargetUser);
+    method public void onSwitchUser(@Nullable com.android.server.SystemService.TargetUser, @NonNull com.android.server.SystemService.TargetUser);
+    method public void onUnlockUser(@NonNull com.android.server.SystemService.TargetUser);
+    method protected final void publishBinderService(@NonNull String, @NonNull android.os.IBinder);
+    method protected final void publishBinderService(@NonNull String, @NonNull android.os.IBinder, boolean);
+    field public static final int PHASE_ACTIVITY_MANAGER_READY = 550; // 0x226
+    field public static final int PHASE_BOOT_COMPLETED = 1000; // 0x3e8
+    field public static final int PHASE_DEVICE_SPECIFIC_SERVICES_READY = 520; // 0x208
+    field public static final int PHASE_LOCK_SETTINGS_READY = 480; // 0x1e0
+    field public static final int PHASE_SYSTEM_SERVICES_READY = 500; // 0x1f4
+    field public static final int PHASE_THIRD_PARTY_APPS_CAN_START = 600; // 0x258
+    field public static final int PHASE_WAIT_FOR_DEFAULT_DISPLAY = 100; // 0x64
+  }
+
+  public static final class SystemService.TargetUser {
+    method @NonNull public android.os.UserHandle getUserHandle();
+  }
+
+}
+
diff --git a/services/art-profile b/services/art-profile
index a0338d5..4e113c8 100644
--- a/services/art-profile
+++ b/services/art-profile
@@ -3066,18 +3066,18 @@
 HSPLcom/android/server/am/AppBindRecord;->dumpInIntentBind(Ljava/io/PrintWriter;Ljava/lang/String;)V
 PLcom/android/server/am/AppBindRecord;->toString()Ljava/lang/String;
 PLcom/android/server/am/AppBindRecord;->writeToProto(Landroid/util/proto/ProtoOutputStream;J)V
-PLcom/android/server/am/AppCompactor$1;->onPropertyChanged(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-HSPLcom/android/server/am/AppCompactor$MemCompactionHandler;->handleMessage(Landroid/os/Message;)V
-HSPLcom/android/server/am/AppCompactor;-><init>(Lcom/android/server/am/ActivityManagerService;)V
-HSPLcom/android/server/am/AppCompactor;->access$1000(Lcom/android/server/am/AppCompactor;)V
-HSPLcom/android/server/am/AppCompactor;->access$700(Lcom/android/server/am/AppCompactor;)Lcom/android/server/am/ActivityManagerService;
-HSPLcom/android/server/am/AppCompactor;->access$800(Lcom/android/server/am/AppCompactor;)Ljava/util/ArrayList;
-HSPLcom/android/server/am/AppCompactor;->access$900(Lcom/android/server/am/AppCompactor;)Ljava/util/Random;
-PLcom/android/server/am/AppCompactor;->dump(Ljava/io/PrintWriter;)V
-HSPLcom/android/server/am/AppCompactor;->init()V
-HSPLcom/android/server/am/AppCompactor;->updateCompactionThrottles()V
-HSPLcom/android/server/am/AppCompactor;->updateUseCompaction()V
-HSPLcom/android/server/am/AppCompactor;->useCompaction()Z
+PLcom/android/server/am/CachedAppOptimizer$1;->onPropertyChanged(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+HSPLcom/android/server/am/CachedAppOptimizer$MemCompactionHandler;->handleMessage(Landroid/os/Message;)V
+HSPLcom/android/server/am/CachedAppOptimizer;-><init>(Lcom/android/server/am/ActivityManagerService;)V
+HSPLcom/android/server/am/CachedAppOptimizer;->access$1000(Lcom/android/server/am/CachedAppOptimizer;)V
+HSPLcom/android/server/am/CachedAppOptimizer;->access$700(Lcom/android/server/am/CachedAppOptimizer;)Lcom/android/server/am/ActivityManagerService;
+HSPLcom/android/server/am/CachedAppOptimizer;->access$800(Lcom/android/server/am/CachedAppOptimizer;)Ljava/util/ArrayList;
+HSPLcom/android/server/am/CachedAppOptimizer;->access$900(Lcom/android/server/am/CachedAppOptimizer;)Ljava/util/Random;
+PLcom/android/server/am/CachedAppOptimizer;->dump(Ljava/io/PrintWriter;)V
+HSPLcom/android/server/am/CachedAppOptimizer;->init()V
+HSPLcom/android/server/am/CachedAppOptimizer;->updateCompactionThrottles()V
+HSPLcom/android/server/am/CachedAppOptimizer;->updateUseCompaction()V
+HSPLcom/android/server/am/CachedAppOptimizer;->useCompaction()Z
 PLcom/android/server/am/AppErrorDialog$1;->handleMessage(Landroid/os/Message;)V
 PLcom/android/server/am/AppErrorDialog;-><init>(Landroid/content/Context;Lcom/android/server/am/ActivityManagerService;Lcom/android/server/am/AppErrorDialog$Data;)V
 PLcom/android/server/am/AppErrorDialog;->onClick(Landroid/view/View;)V
@@ -18632,9 +18632,9 @@
 Lcom/android/server/am/ActivityManagerService$UidObserverRegistration;
 Lcom/android/server/am/ActivityManagerService;
 Lcom/android/server/am/AppBindRecord;
-Lcom/android/server/am/AppCompactor$1;
-Lcom/android/server/am/AppCompactor$MemCompactionHandler;
-Lcom/android/server/am/AppCompactor;
+Lcom/android/server/am/CachedAppOptimizer$1;
+Lcom/android/server/am/CachedAppOptimizer$MemCompactionHandler;
+Lcom/android/server/am/CachedAppOptimizer;
 Lcom/android/server/am/AppErrorDialog$Data;
 Lcom/android/server/am/AppErrorResult;
 Lcom/android/server/am/AppErrors$BadProcessInfo;
diff --git a/services/art-profile-boot b/services/art-profile-boot
index e09424b..fe4178a 100644
--- a/services/art-profile-boot
+++ b/services/art-profile-boot
@@ -538,7 +538,7 @@
 Lcom/android/server/am/ActivityManagerService;->updateLowMemStateLocked(III)Z
 Lcom/android/server/wm/ConfigurationContainer;->getWindowConfiguration()Landroid/app/WindowConfiguration;
 Lcom/android/server/am/OomAdjuster;->applyOomAdjLocked(Lcom/android/server/am/ProcessRecord;ZJJ)Z
-Lcom/android/server/am/AppCompactor;->useCompaction()Z
+Lcom/android/server/am/CachedAppOptimizer;->useCompaction()Z
 Lcom/android/server/am/ProcessList;->procStatesDifferForMem(II)Z
 Lcom/android/server/am/ActivityManagerService;->dispatchUidsChanged()V
 Lcom/android/server/audio/AudioService$VolumeStreamState;->setIndex(IILjava/lang/String;)Z
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 7b95ab5..2c229b4 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -2538,7 +2538,6 @@
                 KeyValueBackupJob.schedule(mUserId, mContext, mConstants);
             } else {
                 if (DEBUG) Slog.v(TAG, "Scheduling immediate backup pass");
-                synchronized (mQueueLock) {
                     // Fire the intent that kicks off the whole shebang...
                     try {
                         mRunBackupIntent.send();
@@ -2546,10 +2545,8 @@
                         // should never happen
                         Slog.e(TAG, "run-backup intent cancelled!");
                     }
-
                     // ...and cancel any pending scheduled job, because we've just superseded it
                     KeyValueBackupJob.cancel(mUserId, mContext);
-                }
             }
         } finally {
             Binder.restoreCallingIdentity(oldId);
diff --git a/services/core/java/android/app/usage/UsageStatsManagerInternal.java b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
index 2f8c506..f3647602 100644
--- a/services/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -23,8 +23,6 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 
-import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
-
 import java.util.List;
 import java.util.Set;
 
@@ -198,6 +196,12 @@
             long beginTime, long endTime, boolean obfuscateInstantApps);
 
     /**
+     * Returns the events for the user in the given time period.
+     */
+    public abstract UsageEvents queryEventsForUser(@UserIdInt int userId, long beginTime,
+            long endTime, boolean shouldObfuscateInstantApps);
+
+    /**
      * Used to persist the last time a job was run for this app, in order to make decisions later
      * whether a job should be deferred until later. The time passed in should be in elapsed
      * realtime since boot.
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 5435cba..c1e23e4 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -79,7 +79,6 @@
 import android.net.IpMemoryStore;
 import android.net.IpPrefix;
 import android.net.LinkProperties;
-import android.net.LinkProperties.CompareResult;
 import android.net.MatchAllNetworkSpecifier;
 import android.net.NattSocketKeepalive;
 import android.net.Network;
@@ -87,7 +86,6 @@
 import android.net.NetworkAgentConfig;
 import android.net.NetworkCapabilities;
 import android.net.NetworkConfig;
-import android.net.NetworkFactory;
 import android.net.NetworkInfo;
 import android.net.NetworkInfo.DetailedState;
 import android.net.NetworkMonitorManager;
@@ -114,6 +112,7 @@
 import android.net.metrics.NetworkEvent;
 import android.net.netlink.InetDiagMessage;
 import android.net.shared.PrivateDnsConfig;
+import android.net.util.LinkPropertiesUtils.CompareResult;
 import android.net.util.MultinetworkPolicyTracker;
 import android.net.util.NetdService;
 import android.os.Binder;
@@ -2843,26 +2842,11 @@
             return true;
         }
 
-        // TODO: delete when direct use of registerNetworkFactory is no longer supported.
-        private boolean maybeHandleNetworkFactoryMessage(Message msg) {
-            switch (msg.what) {
-                default:
-                    return false;
-                case NetworkFactory.EVENT_UNFULFILLABLE_REQUEST: {
-                    handleReleaseNetworkRequest((NetworkRequest) msg.obj, msg.sendingUid,
-                            /* callOnUnavailable */ true);
-                    break;
-                }
-            }
-            return true;
-        }
-
         @Override
         public void handleMessage(Message msg) {
             if (!maybeHandleAsyncChannelMessage(msg)
                     && !maybeHandleNetworkMonitorMessage(msg)
-                    && !maybeHandleNetworkAgentInfoMessage(msg)
-                    && !maybeHandleNetworkFactoryMessage(msg)) {
+                    && !maybeHandleNetworkAgentInfoMessage(msg)) {
                 maybeHandleNetworkAgentMessage(msg);
             }
         }
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index db54214..396b977 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -2855,8 +2855,10 @@
      */
     @Override
     public void startCheckpoint(int numTries) throws RemoteException {
-        // Only the system process is permitted to start checkpoints
-        if (Binder.getCallingUid() != android.os.Process.SYSTEM_UID) {
+        // Only the root, system_server and shell processes are permitted to start checkpoints
+        final int callingUid = Binder.getCallingUid();
+        if (callingUid != Process.SYSTEM_UID && callingUid != Process.ROOT_UID
+                && callingUid != Process.SHELL_UID) {
             throw new SecurityException("no permission to start filesystem checkpoint");
         }
 
diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java
index f46b9ae..b1584fe 100644
--- a/services/core/java/com/android/server/SystemService.java
+++ b/services/core/java/com/android/server/SystemService.java
@@ -21,6 +21,9 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.SystemApi.Client;
+import android.annotation.SystemApi.Process;
 import android.annotation.UserIdInt;
 import android.app.ActivityThread;
 import android.content.Context;
@@ -62,7 +65,7 @@
  *
  * {@hide}
  */
-//@SystemApi(client = Client.MODULE_LIBRARIES, process = Process.SYSTEM_SERVER)
+@SystemApi(client = Client.MODULE_LIBRARIES, process = Process.SYSTEM_SERVER)
 public abstract class SystemService {
 
     /** @hide */
@@ -129,7 +132,7 @@
      * Class representing user in question in the lifecycle callbacks.
      * @hide
      */
-    //@SystemApi(client = Client.MODULE_LIBRARIES, process = Process.SYSTEM_SERVER)
+    @SystemApi(client = Client.MODULE_LIBRARIES, process = Process.SYSTEM_SERVER)
     public static final class TargetUser {
         @NonNull
         private final UserInfo mUserInfo;
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index c0f43a8..e7f7846 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -25,11 +25,14 @@
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManagerInternal;
+import android.util.ArrayMap;
 import android.util.Slog;
 
 import com.android.server.SystemService.TargetUser;
 import com.android.server.utils.TimingsTraceAndSlog;
 
+import dalvik.system.PathClassLoader;
+
 import java.io.File;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
@@ -63,6 +66,9 @@
     // Services that should receive lifecycle events.
     private final ArrayList<SystemService> mServices = new ArrayList<SystemService>();
 
+    // Map of paths to PathClassLoader, so we don't load the same path multiple times.
+    private final ArrayMap<String, PathClassLoader> mLoadedPaths = new ArrayMap<>();
+
     private int mCurrentPhase = -1;
 
     private UserManagerInternal mUserManagerInternal;
@@ -76,20 +82,46 @@
      *
      * @return The service instance.
      */
-    @SuppressWarnings("unchecked")
     public SystemService startService(String className) {
-        final Class<SystemService> serviceClass;
+        final Class<SystemService> serviceClass = loadClassFromLoader(className,
+                this.getClass().getClassLoader());
+        return startService(serviceClass);
+    }
+
+    /**
+     * Starts a service by class name and a path that specifies the jar where the service lives.
+     *
+     * @return The service instance.
+     */
+    public SystemService startServiceFromJar(String className, String path) {
+        PathClassLoader pathClassLoader = mLoadedPaths.get(path);
+        if (pathClassLoader == null) {
+            // NB: the parent class loader should always be the system server class loader.
+            // Changing it has implications that require discussion with the mainline team.
+            pathClassLoader = new PathClassLoader(path, this.getClass().getClassLoader());
+            mLoadedPaths.put(path, pathClassLoader);
+        }
+        final Class<SystemService> serviceClass = loadClassFromLoader(className, pathClassLoader);
+        return startService(serviceClass);
+    }
+
+    /*
+     * Loads and initializes a class from the given classLoader. Returns the class.
+     */
+    @SuppressWarnings("unchecked")
+    private static Class<SystemService> loadClassFromLoader(String className,
+            ClassLoader classLoader) {
         try {
-            serviceClass = (Class<SystemService>)Class.forName(className);
+            return (Class<SystemService>) Class.forName(className, true, classLoader);
         } catch (ClassNotFoundException ex) {
-            Slog.i(TAG, "Starting " + className);
             throw new RuntimeException("Failed to create service " + className
-                    + ": service class not found, usually indicates that the caller should "
+                    + " from class loader " + classLoader.toString() + ": service class not "
+                    + "found, usually indicates that the caller should "
                     + "have called PackageManager.hasSystemFeature() to check whether the "
                     + "feature is available on this device before trying to start the "
-                    + "services that implement it", ex);
+                    + "services that implement it. Also ensure that the correct path for the "
+                    + "classloader is supplied, if applicable.", ex);
         }
-        return startService(serviceClass);
     }
 
     /**
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 27e0d52..5a56a9f 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -61,7 +61,6 @@
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
 import android.util.DebugUtils;
-import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.StatsLog;
@@ -164,8 +163,7 @@
     private int mHapticFeedbackIntensity;
     private int mNotificationIntensity;
     private int mRingIntensity;
-    private SparseArray<Pair<VibrationEffect, VibrationAttributes>> mAlwaysOnEffects =
-            new SparseArray<>();
+    private SparseArray<Vibration> mAlwaysOnEffects = new SparseArray<>();
 
     static native boolean vibratorExists();
     static native void vibratorInit();
@@ -461,6 +459,10 @@
                     Settings.System.getUriFor(Settings.System.RING_VIBRATION_INTENSITY),
                     true, mSettingObserver, UserHandle.USER_ALL);
 
+            mContext.getContentResolver().registerContentObserver(
+                    Settings.Global.getUriFor(Settings.Global.ZEN_MODE),
+                    true, mSettingObserver, UserHandle.USER_ALL);
+
             mContext.registerReceiver(new BroadcastReceiver() {
                 @Override
                 public void onReceive(Context context, Intent intent) {
@@ -508,7 +510,8 @@
     }
 
     @Override // Binder call
-    public boolean setAlwaysOnEffect(int id, VibrationEffect effect, VibrationAttributes attrs) {
+    public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, VibrationEffect effect,
+            VibrationAttributes attrs) {
         if (!hasPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)) {
             throw new SecurityException("Requires VIBRATE_ALWAYS_ON permission");
         }
@@ -518,8 +521,8 @@
         }
         if (effect == null) {
             synchronized (mLock) {
-                mAlwaysOnEffects.delete(id);
-                vibratorAlwaysOnDisable(id);
+                mAlwaysOnEffects.delete(alwaysOnId);
+                vibratorAlwaysOnDisable(alwaysOnId);
             }
         } else {
             if (!verifyVibrationEffect(effect)) {
@@ -529,13 +532,11 @@
                 Slog.e(TAG, "Only prebaked effects supported for always-on.");
                 return false;
             }
-            if (attrs == null) {
-                attrs = new VibrationAttributes.Builder()
-                        .build();
-            }
+            attrs = fixupVibrationAttributes(attrs);
             synchronized (mLock) {
-                mAlwaysOnEffects.put(id, Pair.create(effect, attrs));
-                updateAlwaysOnLocked(id, effect, attrs);
+                Vibration vib = new Vibration(null, effect, attrs, uid, opPkg, null);
+                mAlwaysOnEffects.put(alwaysOnId, vib);
+                updateAlwaysOnLocked(alwaysOnId, vib);
             }
         }
         return true;
@@ -575,6 +576,23 @@
         return true;
     }
 
+    private VibrationAttributes fixupVibrationAttributes(VibrationAttributes attrs) {
+        if (attrs == null) {
+            attrs = DEFAULT_ATTRIBUTES;
+        }
+        if (shouldBypassDnd(attrs)) {
+            if (!(hasPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+                    || hasPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+                    || hasPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING))) {
+                final int flags = attrs.getFlags()
+                        & ~VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
+                attrs = new VibrationAttributes.Builder(attrs).replaceFlags(flags).build();
+            }
+        }
+
+        return attrs;
+    }
+
     private static long[] getLongIntArray(Resources r, int resid) {
         int[] ar = r.getIntArray(resid);
         if (ar == null) {
@@ -604,19 +622,7 @@
                 return;
             }
 
-            if (attrs == null) {
-                attrs = DEFAULT_ATTRIBUTES;
-            }
-
-            if (shouldBypassDnd(attrs)) {
-                if (!(hasPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
-                        || hasPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
-                        || hasPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING))) {
-                    final int flags = attrs.getFlags()
-                            & ~VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
-                    attrs = new VibrationAttributes.Builder(attrs).replaceFlags(flags).build();
-                }
-            }
+            attrs = fixupVibrationAttributes(attrs);
 
             // If our current vibration is longer than the new vibration and is the same amplitude,
             // then just let the current one finish.
@@ -777,29 +783,8 @@
     private void startVibrationLocked(final Vibration vib) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked");
         try {
-            if (!isAllowedToVibrateLocked(vib)) {
-                return;
-            }
-
             final int intensity = getCurrentIntensityLocked(vib);
-            if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) {
-                return;
-            }
-
-            if (vib.isRingtone() && !shouldVibrateForRingtone()) {
-                if (DEBUG) {
-                    Slog.e(TAG, "Vibrate ignored, not vibrating for ringtones");
-                }
-                return;
-            }
-
-            final int mode = getAppOpMode(vib);
-            if (mode != AppOpsManager.MODE_ALLOWED) {
-                if (mode == AppOpsManager.MODE_ERRORED) {
-                    // We might be getting calls from within system_server, so we don't actually
-                    // want to throw a SecurityException here.
-                    Slog.w(TAG, "Would be an error: vibrate from uid " + vib.uid);
-                }
+            if (!shouldVibrate(vib, intensity)) {
                 return;
             }
             applyVibrationIntensityScalingLocked(vib, intensity);
@@ -958,6 +943,35 @@
         return mode;
     }
 
+    private boolean shouldVibrate(Vibration vib, int intensity) {
+        if (!isAllowedToVibrateLocked(vib)) {
+            return false;
+        }
+
+        if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) {
+            return false;
+        }
+
+        if (vib.isRingtone() && !shouldVibrateForRingtone()) {
+            if (DEBUG) {
+                Slog.e(TAG, "Vibrate ignored, not vibrating for ringtones");
+            }
+            return false;
+        }
+
+        final int mode = getAppOpMode(vib);
+        if (mode != AppOpsManager.MODE_ALLOWED) {
+            if (mode == AppOpsManager.MODE_ERRORED) {
+                // We might be getting calls from within system_server, so we don't actually
+                // want to throw a SecurityException here.
+                Slog.w(TAG, "Would be an error: vibrate from uid " + vib.uid);
+            }
+            return false;
+        }
+
+        return true;
+    }
+
     @GuardedBy("mLock")
     private void reportFinishVibrationLocked() {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked");
@@ -1069,14 +1083,12 @@
                 mVibrator.getDefaultRingVibrationIntensity(), UserHandle.USER_CURRENT);
     }
 
-    private void updateAlwaysOnLocked(int id, VibrationEffect effect, VibrationAttributes attrs) {
-        // TODO: Check DND and LowPower settings
-        final Vibration vib = new Vibration(null, effect, attrs, 0, null, null);
+    private void updateAlwaysOnLocked(int id, Vibration vib) {
         final int intensity = getCurrentIntensityLocked(vib);
-        if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) {
+        if (!shouldVibrate(vib, intensity)) {
             vibratorAlwaysOnDisable(id);
         } else {
-            final VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
+            final VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) vib.effect;
             final int strength = intensityToEffectStrength(intensity);
             vibratorAlwaysOnEnable(id, prebaked.getId(), strength);
         }
@@ -1085,8 +1097,8 @@
     private void updateAlwaysOnLocked() {
         for (int i = 0; i < mAlwaysOnEffects.size(); i++) {
             int id = mAlwaysOnEffects.keyAt(i);
-            Pair<VibrationEffect, VibrationAttributes> pair = mAlwaysOnEffects.valueAt(i);
-            updateAlwaysOnLocked(id, pair.first, pair.second);
+            Vibration vib = mAlwaysOnEffects.valueAt(i);
+            updateAlwaysOnLocked(id, vib);
         }
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 8f6d981..883e7c6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2555,7 +2555,7 @@
             Process.setThreadGroupAndCpuset(BackgroundThread.get().getThreadId(),
                     Process.THREAD_GROUP_SYSTEM);
             Process.setThreadGroupAndCpuset(
-                    mOomAdjuster.mAppCompact.mCompactionThread.getThreadId(),
+                    mOomAdjuster.mCachedAppOptimizer.mCachedAppOptimizerThread.getThreadId(),
                     Process.THREAD_GROUP_SYSTEM);
         } catch (Exception e) {
             Slog.w(TAG, "Setting background thread cpuset failed");
@@ -5304,7 +5304,7 @@
                                 String data, Bundle extras, boolean ordered,
                                 boolean sticky, int sendingUser) {
                             synchronized (ActivityManagerService.this) {
-                                mOomAdjuster.mAppCompact.compactAllSystem();
+                                mOomAdjuster.mCachedAppOptimizer.compactAllSystem();
                                 requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, false);
                             }
                         }
@@ -9000,7 +9000,7 @@
             final long timeSinceLastIdle = now - mLastIdleTime;
 
             // Compact all non-zygote processes to freshen up the page cache.
-            mOomAdjuster.mAppCompact.compactAllSystem();
+            mOomAdjuster.mCachedAppOptimizer.compactAllSystem();
 
             final long lowRamSinceLastIdle = getLowRamTimeSinceIdle(now);
             mLastIdleTime = now;
@@ -9108,13 +9108,14 @@
             final Resources res = mContext.getResources();
             mAppErrors.loadAppsNotReportingCrashesFromConfigLocked(res.getString(
                     com.android.internal.R.string.config_appsNotReportingCrashes));
-            mUserController.mUserSwitchUiEnabled = !res.getBoolean(
+            final boolean userSwitchUiEnabled = !res.getBoolean(
                     com.android.internal.R.bool.config_customUserSwitchUi);
-            mUserController.mMaxRunningUsers = res.getInteger(
+            final int maxRunningUsers = res.getInteger(
                     com.android.internal.R.integer.config_multiuserMaxRunningUsers);
-            mUserController.mDelayUserDataLocking = res.getBoolean(
+            final boolean delayUserDataLocking = res.getBoolean(
                     com.android.internal.R.bool.config_multiuserDelayUserDataLocking);
-
+            mUserController.setInitialConfig(userSwitchUiEnabled, maxRunningUsers,
+                    delayUserDataLocking);
             mWaitForNetworkTimeoutMs = waitForNetworkTimeoutMs;
             mPssDeferralTime = pssDeferralMs;
         }
@@ -10020,7 +10021,7 @@
 
         synchronized(this) {
             mConstants.dump(pw);
-            mOomAdjuster.dumpAppCompactorSettings(pw);
+            mOomAdjuster.dumpCachedAppOptimizerSettings(pw);
             pw.println();
             if (dumpAll) {
                 pw.println("-------------------------------------------------------------------------------");
@@ -10425,7 +10426,7 @@
             } else if ("settings".equals(cmd)) {
                 synchronized (this) {
                     mConstants.dump(pw);
-                    mOomAdjuster.dumpAppCompactorSettings(pw);
+                    mOomAdjuster.dumpCachedAppOptimizerSettings(pw);
                 }
             } else if ("services".equals(cmd) || "s".equals(cmd)) {
                 if (dumpClient) {
@@ -17942,7 +17943,31 @@
 
     @Override
     public int stopUser(final int userId, boolean force, final IStopUserCallback callback) {
-        return mUserController.stopUser(userId, force, callback, null /* keyEvictedCallback */);
+        return mUserController.stopUser(userId, force, /* allowDelayedLocking= */ false,
+                /* callback= */ callback, /* keyEvictedCallback= */ null);
+    }
+
+    /**
+     * Stops user but allow delayed locking. Delayed locking keeps user unlocked even after
+     * stopping only if {@code config_multiuserDelayUserDataLocking} overlay is set true.
+     *
+     * <p>When delayed locking is not enabled through the overlay, this call becomes the same
+     * with {@link #stopUser(int, boolean, IStopUserCallback)} call.
+     *
+     * @param userId User id to stop.
+     * @param force Force stop the user even if the user is related with system user or current
+     *              user.
+     * @param callback Callback called when user has stopped.
+     *
+     * @return {@link ActivityManager#USER_OP_SUCCESS} when user is stopped successfully. Returns
+     *         other {@code ActivityManager#USER_OP_*} codes for failure.
+     *
+     */
+    @Override
+    public int stopUserWithDelayedLocking(final int userId, boolean force,
+            final IStopUserCallback callback) {
+        return mUserController.stopUser(userId, force, /* allowDelayedLocking= */ true,
+                /* callback= */ callback, /* keyEvictedCallback= */ null);
     }
 
     @Override
@@ -18348,7 +18373,7 @@
 
         @Override
         public int getMaxRunningUsers() {
-            return mUserController.mMaxRunningUsers;
+            return mUserController.getMaxRunningUsers();
         }
 
         @Override
diff --git a/services/core/java/com/android/server/am/AppCompactor.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
similarity index 97%
rename from services/core/java/com/android/server/am/AppCompactor.java
rename to services/core/java/com/android/server/am/CachedAppOptimizer.java
index b7e2065..3ca5ebc 100644
--- a/services/core/java/com/android/server/am/AppCompactor.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -51,7 +51,7 @@
 import java.util.Random;
 import java.util.Set;
 
-public final class AppCompactor {
+public final class CachedAppOptimizer {
 
     // Flags stored in the DeviceConfig API.
     @VisibleForTesting static final String KEY_USE_COMPACTION = "use_compaction";
@@ -122,7 +122,7 @@
      * that will wipe out the cpuset assignment for system_server threads.
      * Accordingly, this is in the AMS constructor.
      */
-    final ServiceThread mCompactionThread;
+    final ServiceThread mCachedAppOptimizerThread;
 
     private final ArrayList<ProcessRecord> mPendingCompactionProcesses =
             new ArrayList<ProcessRecord>();
@@ -214,15 +214,15 @@
     private int mPersistentCompactionCount;
     private int mBfgsCompactionCount;
 
-    public AppCompactor(ActivityManagerService am) {
+    public CachedAppOptimizer(ActivityManagerService am) {
         mAm = am;
-        mCompactionThread = new ServiceThread("CompactionThread",
+        mCachedAppOptimizerThread = new ServiceThread("CachedAppOptimizerThread",
                 THREAD_PRIORITY_FOREGROUND, true);
         mProcStateThrottle = new HashSet<>();
     }
 
     @VisibleForTesting
-    AppCompactor(ActivityManagerService am, PropertyChangedCallbackForTest callback) {
+    CachedAppOptimizer(ActivityManagerService am, PropertyChangedCallbackForTest callback) {
         this(am);
         mTestCallback = callback;
     }
@@ -243,7 +243,7 @@
             updateFullDeltaRssThrottle();
             updateProcStateThrottle();
         }
-        Process.setThreadGroupAndCpuset(mCompactionThread.getThreadId(),
+        Process.setThreadGroupAndCpuset(mCachedAppOptimizerThread.getThreadId(),
                 Process.THREAD_GROUP_SYSTEM);
     }
 
@@ -258,7 +258,7 @@
 
     @GuardedBy("mAm")
     void dump(PrintWriter pw) {
-        pw.println("AppCompactor settings");
+        pw.println("CachedAppOptimizer settings");
         synchronized (mPhenotypeFlagLock) {
             pw.println("  " + KEY_USE_COMPACTION + "=" + mUseCompaction);
             pw.println("  " + KEY_COMPACT_ACTION_1 + "=" + mCompactActionSome);
@@ -300,7 +300,7 @@
         app.reqCompactAction = COMPACT_PROCESS_SOME;
         mPendingCompactionProcesses.add(app);
         mCompactionHandler.sendMessage(
-            mCompactionHandler.obtainMessage(
+                mCompactionHandler.obtainMessage(
                 COMPACT_PROCESS_MSG, app.setAdj, app.setProcState));
     }
 
@@ -309,7 +309,7 @@
         app.reqCompactAction = COMPACT_PROCESS_FULL;
         mPendingCompactionProcesses.add(app);
         mCompactionHandler.sendMessage(
-            mCompactionHandler.obtainMessage(
+                mCompactionHandler.obtainMessage(
                 COMPACT_PROCESS_MSG, app.setAdj, app.setProcState));
 
     }
@@ -362,8 +362,8 @@
     private void updateUseCompaction() {
         mUseCompaction = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                     KEY_USE_COMPACTION, DEFAULT_USE_COMPACTION);
-        if (mUseCompaction && !mCompactionThread.isAlive()) {
-            mCompactionThread.start();
+        if (mUseCompaction && !mCachedAppOptimizerThread.isAlive()) {
+            mCachedAppOptimizerThread.start();
             mCompactionHandler = new MemCompactionHandler();
         }
     }
@@ -521,7 +521,7 @@
 
     private final class MemCompactionHandler extends Handler {
         private MemCompactionHandler() {
-            super(mCompactionThread.getLooper());
+            super(mCachedAppOptimizerThread.getLooper());
         }
 
         @Override
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 0fc885a..f86d6a7 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -122,9 +122,9 @@
     PowerManagerInternal mLocalPowerManager;
 
     /**
-     * Service for compacting background apps.
+     * Service for optimizing resource usage from background apps.
      */
-    AppCompactor mAppCompact;
+    CachedAppOptimizer mCachedAppOptimizer;
 
     ActivityManagerConstants mConstants;
 
@@ -197,7 +197,7 @@
 
         mLocalPowerManager = LocalServices.getService(PowerManagerInternal.class);
         mConstants = mService.mConstants;
-        mAppCompact = new AppCompactor(mService);
+        mCachedAppOptimizer = new CachedAppOptimizer(mService);
 
         mProcessGroupHandler = new Handler(adjusterThread.getLooper(), msg -> {
             final int pid = msg.arg1;
@@ -224,7 +224,7 @@
     }
 
     void initSettings() {
-        mAppCompact.init();
+        mCachedAppOptimizer.init();
     }
 
     /**
@@ -1978,7 +1978,7 @@
         int changes = 0;
 
         // don't compact during bootup
-        if (mAppCompact.useCompaction() && mService.mBooted) {
+        if (mCachedAppOptimizer.useCompaction() && mService.mBooted) {
             // Cached and prev/home compaction
             if (app.curAdj != app.setAdj) {
                 // Perform a minor compaction when a perceptible app becomes the prev/home app
@@ -1987,26 +1987,26 @@
                 if (app.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ &&
                         (app.curAdj == ProcessList.PREVIOUS_APP_ADJ ||
                                 app.curAdj == ProcessList.HOME_APP_ADJ)) {
-                    mAppCompact.compactAppSome(app);
+                    mCachedAppOptimizer.compactAppSome(app);
                 } else if ((app.setAdj < ProcessList.CACHED_APP_MIN_ADJ
                                 || app.setAdj > ProcessList.CACHED_APP_MAX_ADJ)
                         && app.curAdj >= ProcessList.CACHED_APP_MIN_ADJ
                         && app.curAdj <= ProcessList.CACHED_APP_MAX_ADJ) {
-                    mAppCompact.compactAppFull(app);
+                    mCachedAppOptimizer.compactAppFull(app);
                 }
             } else if (mService.mWakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE
                     && app.setAdj < ProcessList.FOREGROUND_APP_ADJ
                     // Because these can fire independent of oom_adj/procstate changes, we need
                     // to throttle the actual dispatch of these requests in addition to the
                     // processing of the requests. As a result, there is throttling both here
-                    // and in AppCompactor.
-                    && mAppCompact.shouldCompactPersistent(app, now)) {
-                mAppCompact.compactAppPersistent(app);
+                    // and in CachedAppOptimizer.
+                    && mCachedAppOptimizer.shouldCompactPersistent(app, now)) {
+                mCachedAppOptimizer.compactAppPersistent(app);
             } else if (mService.mWakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE
                     && app.getCurProcState()
                         == ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
-                    && mAppCompact.shouldCompactBFGS(app, now)) {
-                mAppCompact.compactAppBfgs(app);
+                    && mCachedAppOptimizer.shouldCompactBFGS(app, now)) {
+                mCachedAppOptimizer.compactAppBfgs(app);
             }
         }
 
@@ -2439,7 +2439,7 @@
     }
 
     @GuardedBy("mService")
-    void dumpAppCompactorSettings(PrintWriter pw) {
-        mAppCompact.dump(pw);
+    void dumpCachedAppOptimizerSettings(PrintWriter pw) {
+        mCachedAppOptimizer.dump(pw);
     }
 }
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index e11008c..b7f867d 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -647,11 +647,10 @@
         // Get this after boot, and won't be changed until it's rebooted, as we don't
         // want some apps enabled while some apps disabled
         mAppDataIsolationEnabled =
-                SystemProperties.getBoolean(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, false);
+                SystemProperties.getBoolean(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, true);
         mAppDataIsolationWhitelistedApps = new ArrayList<>(
                 SystemConfig.getInstance().getAppDataIsolationWhitelistedApps());
 
-
         if (sKillHandler == null) {
             sKillThread = new ServiceThread(TAG + ":kill",
                     THREAD_PRIORITY_BACKGROUND, true /* allowIo */);
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 8ae18ff6..f3a2e70 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -93,7 +93,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.Preconditions;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
@@ -161,7 +160,8 @@
      * <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;
+    @GuardedBy("mLock")
+    private int mMaxRunningUsers;
 
     // Lock for internal state.
     private final Object mLock = new Object();
@@ -213,7 +213,8 @@
     private final RemoteCallbackList<IUserSwitchObserver> mUserSwitchObservers
             = new RemoteCallbackList<>();
 
-    boolean mUserSwitchUiEnabled = true;
+    @GuardedBy("mLock")
+    private boolean mUserSwitchUiEnabled = true;
 
     /**
      * Currently active user switch callbacks.
@@ -246,10 +247,11 @@
     /**
      * In this mode, user is always stopped when switched out but locking of user data is
      * postponed until total number of unlocked users in the system reaches mMaxRunningUsers.
-     * Once total number of unlocked users reach mMaxRunningUsers, least recentely used user
+     * Once total number of unlocked users reach mMaxRunningUsers, least recently used user
      * will be locked.
      */
-    boolean mDelayUserDataLocking;
+    @GuardedBy("mLock")
+    private boolean mDelayUserDataLocking;
     /**
      * Keep track of last active users for mDelayUserDataLocking.
      * The latest stopped user is placed in front while the least recently stopped user in back.
@@ -275,6 +277,33 @@
         updateStartedUserArrayLU();
     }
 
+    void setInitialConfig(boolean userSwitchUiEnabled, int maxRunningUsers,
+            boolean delayUserDataLocking) {
+        synchronized (mLock) {
+            mUserSwitchUiEnabled = userSwitchUiEnabled;
+            mMaxRunningUsers = maxRunningUsers;
+            mDelayUserDataLocking = delayUserDataLocking;
+        }
+    }
+
+    private boolean isUserSwitchUiEnabled() {
+        synchronized (mLock) {
+            return mUserSwitchUiEnabled;
+        }
+    }
+
+    int getMaxRunningUsers() {
+        synchronized (mLock) {
+            return mMaxRunningUsers;
+        }
+    }
+
+    private boolean isDelayUserDataLockingEnabled() {
+        synchronized (mLock) {
+            return mDelayUserDataLocking;
+        }
+    }
+
     void finishUserSwitch(UserState uss) {
         // This call holds the AM lock so we post to the handler.
         mHandler.post(() -> {
@@ -321,7 +350,11 @@
                 // Owner/System user and current user can't be stopped
                 continue;
             }
-            if (stopUsersLU(userId, false, null, null) == USER_OP_SUCCESS) {
+            // allowDelayedLocking set here as stopping user is done without any explicit request
+            // from outside.
+            if (stopUsersLU(userId, /* force= */ false, /* allowDelayedLocking= */ true,
+                    /* stopUserCallback= */ null, /* keyEvictedCallback= */ null)
+                    == USER_OP_SUCCESS) {
                 iterator.remove();
             }
         }
@@ -567,8 +600,8 @@
             // intialize it; it should be stopped right away as it's not really a "real" user.
             // TODO(b/143092698): in the long-term, it might be better to add a onCreateUser()
             // callback on SystemService instead.
-            stopUser(userInfo.id, /* force= */ true, /* stopUserCallback= */ null,
-                    /* keyEvictedCallback= */ null);
+            stopUser(userInfo.id, /* force= */ true, /* allowDelayedLocking= */ false,
+                    /* stopUserCallback= */ null, /* keyEvictedCallback= */ null);
             return;
         }
 
@@ -611,7 +644,8 @@
     }
 
     int restartUser(final int userId, final boolean foreground) {
-        return stopUser(userId, /* force */ true, null, new KeyEvictedCallback() {
+        return stopUser(userId, /* force= */ true, /* allowDelayedLocking= */ false,
+                /* stopUserCallback= */ null, new KeyEvictedCallback() {
             @Override
             public void keyEvicted(@UserIdInt int userId) {
                 // Post to the same handler that this callback is called from to ensure the user
@@ -621,15 +655,16 @@
         });
     }
 
-    int stopUser(final int userId, final boolean force, final IStopUserCallback stopUserCallback,
-            KeyEvictedCallback keyEvictedCallback) {
+    int stopUser(final int userId, final boolean force, boolean allowDelayedLocking,
+            final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) {
         checkCallingPermission(INTERACT_ACROSS_USERS_FULL, "stopUser");
         if (userId < 0 || userId == UserHandle.USER_SYSTEM) {
             throw new IllegalArgumentException("Can't stop system user " + userId);
         }
         enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
         synchronized (mLock) {
-            return stopUsersLU(userId, force, stopUserCallback, keyEvictedCallback);
+            return stopUsersLU(userId, force, allowDelayedLocking, stopUserCallback,
+                    keyEvictedCallback);
         }
     }
 
@@ -638,7 +673,7 @@
      * {@link #getUsersToStopLU(int)} to determine the list of users that should be stopped.
      */
     @GuardedBy("mLock")
-    private int stopUsersLU(final int userId, boolean force,
+    private int stopUsersLU(final int userId, boolean force, boolean allowDelayedLocking,
             final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) {
         if (userId == UserHandle.USER_SYSTEM) {
             return USER_OP_ERROR_IS_SYSTEM;
@@ -657,7 +692,8 @@
                 if (force) {
                     Slog.i(TAG,
                             "Force stop user " + userId + ". Related users will not be stopped");
-                    stopSingleUserLU(userId, stopUserCallback, keyEvictedCallback);
+                    stopSingleUserLU(userId, allowDelayedLocking, stopUserCallback,
+                            keyEvictedCallback);
                     return USER_OP_SUCCESS;
                 }
                 return USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
@@ -665,21 +701,64 @@
         }
         if (DEBUG_MU) Slog.i(TAG, "stopUsersLocked usersToStop=" + Arrays.toString(usersToStop));
         for (int userIdToStop : usersToStop) {
-            stopSingleUserLU(userIdToStop,
+            stopSingleUserLU(userIdToStop, allowDelayedLocking,
                     userIdToStop == userId ? stopUserCallback : null,
                     userIdToStop == userId ? keyEvictedCallback : null);
         }
         return USER_OP_SUCCESS;
     }
 
+    /**
+     * Stops a single User. This can also trigger locking user data out depending on device's
+     * config ({@code mDelayUserDataLocking}) and arguments.
+     * User will be unlocked when
+     * - {@code mDelayUserDataLocking} is not set.
+     * - {@code mDelayUserDataLocking} is set and {@code keyEvictedCallback} is non-null.
+     * -
+     *
+     * @param userId User Id to stop and lock the data.
+     * @param allowDelayedLocking When set, do not lock user after stopping. Locking can happen
+     *                            later when number of unlocked users reaches
+     *                            {@code mMaxRunnngUsers}. Note that this is respected only when
+     *                            {@code mDelayUserDataLocking} is set and {@keyEvictedCallback} is
+     *                            null. Otherwise the user will be locked.
+     * @param stopUserCallback Callback to notify that user has stopped.
+     * @param keyEvictedCallback Callback to notify that user has been unlocked.
+     */
     @GuardedBy("mLock")
-    private void stopSingleUserLU(final int userId, final IStopUserCallback stopUserCallback,
+    private void stopSingleUserLU(final int userId, boolean allowDelayedLocking,
+            final IStopUserCallback stopUserCallback,
             KeyEvictedCallback keyEvictedCallback) {
         if (DEBUG_MU) Slog.i(TAG, "stopSingleUserLocked userId=" + userId);
         final UserState uss = mStartedUsers.get(userId);
-        if (uss == null) {
-            // User is not started, nothing to do...  but we do need to
-            // callback if requested.
+        if (uss == null) {  // User is not started
+            // If mDelayUserDataLocking is set and allowDelayedLocking is not set, we need to lock
+            // the requested user as the client wants to stop and lock the user. On the other hand,
+            // having keyEvictedCallback set will lead into locking user if mDelayUserDataLocking
+            // is set as that means client wants to lock the user immediately.
+            // If mDelayUserDataLocking is not set, the user was already locked when it was stopped
+            // and no further action is necessary.
+            if (mDelayUserDataLocking) {
+                if (allowDelayedLocking && keyEvictedCallback != null) {
+                    Slog.wtf(TAG, "allowDelayedLocking set with KeyEvictedCallback, ignore it"
+                            + " and lock user:" + userId, new RuntimeException());
+                    allowDelayedLocking = false;
+                }
+                if (!allowDelayedLocking) {
+                    if (mLastActiveUsers.remove(Integer.valueOf(userId))) {
+                        // should lock the user, user is already gone
+                        final ArrayList<KeyEvictedCallback> keyEvictedCallbacks;
+                        if (keyEvictedCallback != null) {
+                            keyEvictedCallbacks = new ArrayList<>(1);
+                            keyEvictedCallbacks.add(keyEvictedCallback);
+                        } else {
+                            keyEvictedCallbacks = null;
+                        }
+                        dispatchUserLocking(userId, keyEvictedCallbacks);
+                    }
+                }
+            }
+            // We do need to post the stopped callback even though user is already stopped.
             if (stopUserCallback != null) {
                 mHandler.post(() -> {
                     try {
@@ -704,6 +783,7 @@
             mInjector.getUserManagerInternal().setUserState(userId, uss.state);
             updateStartedUserArrayLU();
 
+            final boolean allowDelayyLockingCopied = allowDelayedLocking;
             // Post to handler to obtain amLock
             mHandler.post(() -> {
                 // We are going to broadcast ACTION_USER_STOPPING and then
@@ -718,7 +798,8 @@
                     @Override
                     public void performReceive(Intent intent, int resultCode, String data,
                             Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
-                        mHandler.post(() -> finishUserStopping(userId, uss));
+                        mHandler.post(() -> finishUserStopping(userId, uss,
+                                allowDelayyLockingCopied));
                     }
                 };
 
@@ -734,7 +815,8 @@
         }
     }
 
-    void finishUserStopping(final int userId, final UserState uss) {
+    void finishUserStopping(final int userId, final UserState uss,
+            final boolean allowDelayedLocking) {
         Slog.d(TAG, "UserController event: finishUserStopping(" + userId + ")");
         // On to the next.
         final Intent shutdownIntent = new Intent(Intent.ACTION_SHUTDOWN);
@@ -746,7 +828,7 @@
                 mHandler.post(new Runnable() {
                     @Override
                     public void run() {
-                        finishUserStopped(uss);
+                        finishUserStopped(uss, allowDelayedLocking);
                     }
                 });
             }
@@ -773,7 +855,7 @@
                 Binder.getCallingPid(), userId);
     }
 
-    void finishUserStopped(UserState uss) {
+    void finishUserStopped(UserState uss, boolean allowDelayedLocking) {
         final int userId = uss.mHandle.getIdentifier();
         Slog.d(TAG, "UserController event: finishUserStopped(" + userId + ")");
         final boolean stopped;
@@ -792,7 +874,13 @@
                 mStartedUsers.remove(userId);
                 mUserLru.remove(Integer.valueOf(userId));
                 updateStartedUserArrayLU();
-                userIdToLock = updateUserToLockLU(userId);
+                if (allowDelayedLocking && !keyEvictedCallbacks.isEmpty()) {
+                    Slog.wtf(TAG,
+                            "Delayed locking enabled while KeyEvictedCallbacks not empty, userId:"
+                                    + userId + " callbacks:" + keyEvictedCallbacks);
+                    allowDelayedLocking = false;
+                }
+                userIdToLock = updateUserToLockLU(userId, allowDelayedLocking);
                 if (userIdToLock == UserHandle.USER_NULL) {
                     lockUser = false;
                 }
@@ -826,31 +914,36 @@
             if (!lockUser) {
                 return;
             }
-            final int userIdToLockF = userIdToLock;
-            // Evict the user's credential encryption key. Performed on FgThread to make it
-            // serialized with call to UserManagerService.onBeforeUnlockUser in finishUserUnlocking
-            // to prevent data corruption.
-            FgThread.getHandler().post(() -> {
-                synchronized (mLock) {
-                    if (mStartedUsers.get(userIdToLockF) != null) {
-                        Slog.w(TAG, "User was restarted, skipping key eviction");
-                        return;
-                    }
-                }
-                try {
-                    mInjector.getStorageManager().lockUserKey(userIdToLockF);
-                } catch (RemoteException re) {
-                    throw re.rethrowAsRuntimeException();
-                }
-                if (userIdToLockF == userId) {
-                    for (final KeyEvictedCallback callback : keyEvictedCallbacks) {
-                        callback.keyEvicted(userId);
-                    }
-                }
-            });
+            dispatchUserLocking(userIdToLock, keyEvictedCallbacks);
         }
     }
 
+    private void dispatchUserLocking(@UserIdInt int userId,
+            @Nullable List<KeyEvictedCallback> keyEvictedCallbacks) {
+        // Evict the user's credential encryption key. Performed on FgThread to make it
+        // serialized with call to UserManagerService.onBeforeUnlockUser in finishUserUnlocking
+        // to prevent data corruption.
+        FgThread.getHandler().post(() -> {
+            synchronized (mLock) {
+                if (mStartedUsers.get(userId) != null) {
+                    Slog.w(TAG, "User was restarted, skipping key eviction");
+                    return;
+                }
+            }
+            try {
+                mInjector.getStorageManager().lockUserKey(userId);
+            } catch (RemoteException re) {
+                throw re.rethrowAsRuntimeException();
+            }
+            if (keyEvictedCallbacks == null) {
+                return;
+            }
+            for (int i = 0; i < keyEvictedCallbacks.size(); i++) {
+                keyEvictedCallbacks.get(i).keyEvicted(userId);
+            }
+        });
+    }
+
     /**
      * For mDelayUserDataLocking mode, storage once unlocked is kept unlocked.
      * Total number of unlocked user storage is limited by mMaxRunningUsers.
@@ -861,9 +954,9 @@
      * @return user id to lock. UserHandler.USER_NULL will be returned if no user should be locked.
      */
     @GuardedBy("mLock")
-    private int updateUserToLockLU(@UserIdInt int userId) {
+    private int updateUserToLockLU(@UserIdInt int userId, boolean allowDelayedLocking) {
         int userIdToLock = userId;
-        if (mDelayUserDataLocking && !getUserInfo(userId).isEphemeral()
+        if (mDelayUserDataLocking && allowDelayedLocking && !getUserInfo(userId).isEphemeral()
                 && !hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, userId)) {
             mLastActiveUsers.remove((Integer) userId); // arg should be object, not index
             mLastActiveUsers.add(0, userId);
@@ -945,7 +1038,8 @@
         if (userInfo.isGuest() || userInfo.isEphemeral()) {
             // This is a user to be stopped.
             synchronized (mLock) {
-                stopUsersLU(oldUserId, true, null, null);
+                stopUsersLU(oldUserId, /* force= */ true, /* allowDelayedLocking= */ false,
+                        null, null);
             }
         }
     }
@@ -977,7 +1071,7 @@
         }
         final int profilesToStartSize = profilesToStart.size();
         int i = 0;
-        for (; i < profilesToStartSize && i < (mMaxRunningUsers - 1); ++i) {
+        for (; i < profilesToStartSize && i < (getMaxRunningUsers() - 1); ++i) {
             startUser(profilesToStart.get(i).id, /* foreground= */ false);
         }
         if (i < profilesToStartSize) {
@@ -1094,7 +1188,7 @@
                 return false;
             }
 
-            if (foreground && mUserSwitchUiEnabled) {
+            if (foreground && isUserSwitchUiEnabled()) {
                 t.traceBegin("startFreezingScreen");
                 mInjector.getWindowManager().startFreezingScreen(
                         R.anim.screen_user_exit, R.anim.screen_user_enter);
@@ -1142,9 +1236,11 @@
             if (foreground) {
                 // Make sure the old user is no longer considering the display to be on.
                 mInjector.reportGlobalUsageEventLocked(UsageEvents.Event.SCREEN_NON_INTERACTIVE);
+                boolean userSwitchUiEnabled;
                 synchronized (mLock) {
                     mCurrentUserId = userId;
                     mTargetUserId = UserHandle.USER_NULL; // reset, mCurrentUserId has caught up
+                    userSwitchUiEnabled = mUserSwitchUiEnabled;
                 }
                 mInjector.updateUserConfiguration();
                 updateCurrentProfileIds();
@@ -1152,7 +1248,7 @@
                 mInjector.reportCurWakefulnessUsageEvent();
                 // Once the internal notion of the active user has switched, we lock the device
                 // with the option to show the user switcher on the keyguard.
-                if (mUserSwitchUiEnabled) {
+                if (userSwitchUiEnabled) {
                     mInjector.getWindowManager().setSwitchingUser(true);
                     mInjector.getWindowManager().lockNow(null);
                 }
@@ -1391,10 +1487,12 @@
             Slog.w(TAG, "Cannot switch to User #" + targetUserId + ": not a full user");
             return false;
         }
+        boolean userSwitchUiEnabled;
         synchronized (mLock) {
             mTargetUserId = targetUserId;
+            userSwitchUiEnabled = mUserSwitchUiEnabled;
         }
-        if (mUserSwitchUiEnabled) {
+        if (userSwitchUiEnabled) {
             UserInfo currentUserInfo = getUserInfo(currentUserId);
             Pair<UserInfo, UserInfo> userNames = new Pair<>(currentUserInfo, targetUserInfo);
             mUiHandler.removeMessages(START_USER_SWITCH_UI_MSG);
@@ -1458,14 +1556,15 @@
         }
         // If running in background is disabled or mDelayUserDataLocking mode, stop the user.
         boolean disallowRunInBg = hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND,
-                oldUserId) || mDelayUserDataLocking;
+                oldUserId) || isDelayUserDataLockingEnabled();
         if (!disallowRunInBg) {
             return;
         }
         synchronized (mLock) {
             if (DEBUG_MU) Slog.i(TAG, "stopBackgroundUsersIfEnforced stopping " + oldUserId
                     + " and related users");
-            stopUsersLU(oldUserId, false, null, null);
+            stopUsersLU(oldUserId, /* force= */ false, /* allowDelayedLocking= */ true,
+                    null, null);
         }
     }
 
@@ -1551,7 +1650,7 @@
 
     void continueUserSwitch(UserState uss, int oldUserId, int newUserId) {
         Slog.d(TAG, "Continue user switch oldUser #" + oldUserId + ", newUser #" + newUserId);
-        if (mUserSwitchUiEnabled) {
+        if (isUserSwitchUiEnabled()) {
             mInjector.getWindowManager().stopFreezingScreen();
         }
         uss.switching = false;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index cd2272a..eedeeea 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -2193,8 +2193,8 @@
     }
 
     private void enforceModifyAudioRoutingPermission() {
-        if (mContext.checkCallingPermission(
-                    android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+        if (mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.MODIFY_AUDIO_ROUTING)
                 != PackageManager.PERMISSION_GRANTED) {
             throw new SecurityException("Missing MODIFY_AUDIO_ROUTING permission");
         }
@@ -6427,7 +6427,7 @@
                 return false;
             }
             boolean suppress = false;
-            if (resolvedStream == DEFAULT_VOL_STREAM_NO_PLAYBACK && mController != null) {
+            if (resolvedStream != AudioSystem.STREAM_MUSIC && mController != null) {
                 final long now = SystemClock.uptimeMillis();
                 if ((flags & AudioManager.FLAG_SHOW_UI) != 0 && !mVisible) {
                     // ui will become visible
diff --git a/services/core/java/com/android/server/backup/PeopleBackupHelper.java b/services/core/java/com/android/server/backup/PeopleBackupHelper.java
new file mode 100644
index 0000000..e58a051
--- /dev/null
+++ b/services/core/java/com/android/server/backup/PeopleBackupHelper.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.backup;
+
+import android.app.backup.BlobBackupHelper;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
+import com.android.server.people.PeopleServiceInternal;
+
+class PeopleBackupHelper extends BlobBackupHelper {
+
+    private static final String TAG = PeopleBackupHelper.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    // Current schema of the backup state blob.
+    private static final int STATE_VERSION = 1;
+
+    // Key under which conversation infos state blob is committed to backup.
+    private static final String KEY_CONVERSATIONS = "people_conversation_infos";
+
+    private final int mUserId;
+
+    PeopleBackupHelper(int userId) {
+        super(STATE_VERSION, KEY_CONVERSATIONS);
+        mUserId = userId;
+    }
+
+    @Override
+    protected byte[] getBackupPayload(String key) {
+        if (!KEY_CONVERSATIONS.equals(key)) {
+            Slog.w(TAG, "Unexpected backup key " + key);
+            return new byte[0];
+        }
+        PeopleServiceInternal ps = LocalServices.getService(PeopleServiceInternal.class);
+        if (DEBUG) {
+            Slog.d(TAG, "Handling backup of " + key);
+        }
+        return ps.backupConversationInfos(mUserId);
+    }
+
+    @Override
+    protected void applyRestoredPayload(String key, byte[] payload) {
+        if (!KEY_CONVERSATIONS.equals(key)) {
+            Slog.w(TAG, "Unexpected restore key " + key);
+            return;
+        }
+        PeopleServiceInternal ps = LocalServices.getService(PeopleServiceInternal.class);
+        if (DEBUG) {
+            Slog.d(TAG, "Handling restore of " + key);
+        }
+        ps.restoreConversationInfos(mUserId, key, payload);
+    }
+}
diff --git a/services/core/java/com/android/server/backup/SystemBackupAgent.java b/services/core/java/com/android/server/backup/SystemBackupAgent.java
index 35e8f56..1f4563b 100644
--- a/services/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/services/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -55,6 +55,7 @@
     private static final String SHORTCUT_MANAGER_HELPER = "shortcut_manager";
     private static final String ACCOUNT_MANAGER_HELPER = "account_manager";
     private static final String SLICES_HELPER = "slices";
+    private static final String PEOPLE_HELPER = "people";
 
     // These paths must match what the WallpaperManagerService uses.  The leaf *_FILENAME
     // are also used in the full-backup file format, so must not change unless steps are
@@ -99,6 +100,7 @@
         addHelper(SHORTCUT_MANAGER_HELPER, new ShortcutBackupHelper());
         addHelper(ACCOUNT_MANAGER_HELPER, new AccountManagerBackupHelper());
         addHelper(SLICES_HELPER, new SliceBackupHelper(this));
+        addHelper(PEOPLE_HELPER, new PeopleBackupHelper(mUserId));
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index e1a9f3b..8dd3242 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -1400,7 +1400,8 @@
                     if (mCurrentAuthSession.mTokenEscrow != null) {
                         mKeyStore.addAuthToken(mCurrentAuthSession.mTokenEscrow);
                     }
-                    mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded();
+                    mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded(
+                            Utils.getAuthenticationTypeForResult(reason));
                     break;
 
                 case BiometricPrompt.DISMISSED_REASON_NEGATIVE:
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index 19f5358..389763b 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -22,6 +22,7 @@
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricPrompt;
+import android.hardware.biometrics.BiometricPrompt.AuthenticationResultType;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.UserHandle;
@@ -210,4 +211,30 @@
         }
         return biometricManagerCode;
     }
+
+    /**
+     * Converts a {@link BiometricPrompt} dismissal reason to an authentication type at the level of
+     * granularity supported by {@link BiometricPrompt.AuthenticationResult}.
+     *
+     * @param reason The reason that the {@link BiometricPrompt} was dismissed. Must be one of:
+     *               {@link BiometricPrompt#DISMISSED_REASON_CREDENTIAL_CONFIRMED},
+     *               {@link BiometricPrompt#DISMISSED_REASON_BIOMETRIC_CONFIRMED}, or
+     *               {@link BiometricPrompt#DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED}
+     * @return An integer representing the authentication type for {@link
+     *         BiometricPrompt.AuthenticationResult}.
+     * @throws IllegalArgumentException if given an invalid dismissal reason.
+     */
+    public static @AuthenticationResultType int getAuthenticationTypeForResult(int reason) {
+        switch (reason) {
+            case BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED:
+                return BiometricPrompt.AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL;
+
+            case BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED:
+            case BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED:
+                return BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC;
+
+            default:
+                throw new IllegalArgumentException("Unsupported dismissal reason: " + reason);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/connectivity/DataConnectionStats.java b/services/core/java/com/android/server/connectivity/DataConnectionStats.java
index 1b1c546..5010e46 100644
--- a/services/core/java/com/android/server/connectivity/DataConnectionStats.java
+++ b/services/core/java/com/android/server/connectivity/DataConnectionStats.java
@@ -16,6 +16,9 @@
 
 package com.android.server.connectivity;
 
+import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
+import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
+
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -24,6 +27,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.RemoteException;
+import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
@@ -91,7 +95,10 @@
         boolean visible = (simReadyOrUnknown || isCdma()) // we only check the sim state for GSM
                 && hasService()
                 && mDataState == TelephonyManager.DATA_CONNECTED;
-        int networkType = mServiceState.getDataNetworkType();
+        NetworkRegistrationInfo regInfo =
+                mServiceState.getNetworkRegistrationInfo(DOMAIN_PS, TRANSPORT_TYPE_WWAN);
+        int networkType = regInfo == null ? TelephonyManager.NETWORK_TYPE_UNKNOWN
+                : regInfo.getAccessNetworkTechnology();
         if (DEBUG) Log.d(TAG, String.format("Noting data connection for network type %s: %svisible",
                 networkType, visible ? "" : "not "));
         try {
diff --git a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
index 6fa999c..04c792a 100644
--- a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
+++ b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
@@ -26,6 +26,7 @@
 import static android.net.NetworkPolicy.LIMIT_DISABLED;
 import static android.net.NetworkPolicy.WARNING_DISABLED;
 import static android.provider.Settings.Global.NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES;
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
 import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_MULTIPATH;
 import static com.android.server.net.NetworkPolicyManagerService.OPPORTUNISTIC_QUOTA_UNKNOWN;
@@ -46,19 +47,18 @@
 import android.net.NetworkPolicy;
 import android.net.NetworkPolicyManager;
 import android.net.NetworkRequest;
+import android.net.NetworkSpecifier;
 import android.net.NetworkStats;
 import android.net.NetworkTemplate;
-import android.net.StringNetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
+import android.net.Uri;
 import android.os.BestClock;
 import android.os.Handler;
 import android.os.SystemClock;
-import android.net.Uri;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.telephony.TelephonyManager;
-import android.util.DataUnit;
 import android.util.DebugUtils;
-import android.util.Pair;
 import android.util.Range;
 import android.util.Slog;
 
@@ -74,7 +74,6 @@
 import java.time.ZoneOffset;
 import java.time.ZonedDateTime;
 import java.time.temporal.ChronoUnit;
-import java.util.Iterator;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.TimeUnit;
 
@@ -185,7 +184,6 @@
     // Track information on mobile networks as they come and go.
     class MultipathTracker {
         final Network network;
-        final int subId;
         final String subscriberId;
 
         private long mQuota;
@@ -198,13 +196,14 @@
         public MultipathTracker(Network network, NetworkCapabilities nc) {
             this.network = network;
             this.mNetworkCapabilities = new NetworkCapabilities(nc);
-            try {
-                subId = Integer.parseInt(
-                        ((StringNetworkSpecifier) nc.getNetworkSpecifier()).toString());
-            } catch (ClassCastException | NullPointerException | NumberFormatException e) {
+            NetworkSpecifier specifier = nc.getNetworkSpecifier();
+            int subId = INVALID_SUBSCRIPTION_ID;
+            if (specifier instanceof TelephonyNetworkSpecifier) {
+                subId = ((TelephonyNetworkSpecifier) specifier).getSubscriptionId();
+            } else {
                 throw new IllegalStateException(String.format(
-                        "Can't get subId from mobile network %s (%s): %s",
-                        network, nc, e.getMessage()));
+                        "Can't get subId from mobile network %s (%s)",
+                        network, nc));
             }
 
             TelephonyManager tele = mContext.getSystemService(TelephonyManager.class);
diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
index 2179518..2c41557 100644
--- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
@@ -28,7 +28,7 @@
 import android.content.Intent;
 import android.content.res.Resources;
 import android.net.NetworkSpecifier;
-import android.net.StringNetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
 import android.net.wifi.WifiInfo;
 import android.os.UserHandle;
 import android.telephony.SubscriptionManager;
@@ -223,14 +223,8 @@
                     // name has been added to it
                     NetworkSpecifier specifier = nai.networkCapabilities.getNetworkSpecifier();
                     int subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
-                    if (specifier instanceof StringNetworkSpecifier) {
-                        try {
-                            subId = Integer.parseInt(
-                                    ((StringNetworkSpecifier) specifier).specifier);
-                        } catch (NumberFormatException e) {
-                            Slog.e(TAG, "NumberFormatException on "
-                                    + ((StringNetworkSpecifier) specifier).specifier);
-                        }
+                    if (specifier instanceof TelephonyNetworkSpecifier) {
+                        subId = ((TelephonyNetworkSpecifier) specifier).getSubscriptionId();
                     }
 
                     details = mTelephonyManager.createForSubscriptionId(subId)
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index 8498dcb..decb1f9 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -73,7 +73,7 @@
     private static final int GLOBAL_ID = -1;
 
     // The tolerance within which we consider something approximately equals.
-    private static final float EPSILON = 0.001f;
+    private static final float EPSILON = 0.01f;
 
     private final Object mLock = new Object();
     private final Context mContext;
diff --git a/services/core/java/com/android/server/integrity/model/BitOutputStream.java b/services/core/java/com/android/server/integrity/model/BitOutputStream.java
index b8ea041..da778aa 100644
--- a/services/core/java/com/android/server/integrity/model/BitOutputStream.java
+++ b/services/core/java/com/android/server/integrity/model/BitOutputStream.java
@@ -16,17 +16,25 @@
 
 package com.android.server.integrity.model;
 
-import java.util.BitSet;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Arrays;
 
 /** A wrapper class for writing a stream of bits. */
 public class BitOutputStream {
 
-    private BitSet mBitSet;
-    private int mIndex;
+    private static final int BUFFER_SIZE = 4 * 1024;
+    private static final int BYTE_BITS = 8;
 
-    public BitOutputStream() {
-        mBitSet = new BitSet();
-        mIndex = 0;
+    private int mNextBitIndex;
+
+    private final OutputStream mOutputStream;
+    private final byte[] mBuffer;
+
+    public BitOutputStream(OutputStream outputStream) {
+        mBuffer = new byte[BUFFER_SIZE];
+        mNextBitIndex = 0;
+        mOutputStream = outputStream;
     }
 
     /**
@@ -35,15 +43,17 @@
      * @param numOfBits The number of bits used to represent the value.
      * @param value The value to convert to bits.
      */
-    public void setNext(int numOfBits, int value) {
+    public void setNext(int numOfBits, int value) throws IOException {
         if (numOfBits <= 0) {
             return;
         }
-        int offset = 1 << (numOfBits - 1);
+
+        // optional: we can do some clever size checking to "OR" an entire segment of bits instead
+        // of setting bits one by one, but it is probably not worth it.
+        int nextBitMask = 1 << (numOfBits - 1);
         while (numOfBits-- > 0) {
-            mBitSet.set(mIndex, (value & offset) != 0);
-            offset >>>= 1;
-            mIndex++;
+            setNext((value & nextBitMask) != 0);
+            nextBitMask >>>= 1;
         }
     }
 
@@ -52,35 +62,43 @@
      *
      * @param value The value to set the bit to.
      */
-    public void setNext(boolean value) {
-        mBitSet.set(mIndex, value);
-        mIndex++;
+    public void setNext(boolean value) throws IOException {
+        int byteToWrite = mNextBitIndex / BYTE_BITS;
+        if (byteToWrite == BUFFER_SIZE) {
+            mOutputStream.write(mBuffer);
+            reset();
+            byteToWrite = 0;
+        }
+        if (value) {
+            mBuffer[byteToWrite] |= 1 << (BYTE_BITS - 1 - (mNextBitIndex % BYTE_BITS));
+        }
+        mNextBitIndex++;
     }
 
     /** Set the next bit in the stream to true. */
-    public void setNext() {
+    public void setNext() throws IOException {
         setNext(/* value= */ true);
     }
 
-    /** Convert BitSet in big-endian to ByteArray in big-endian. */
-    public byte[] toByteArray() {
-        int bitSetSize = mBitSet.length();
-        int numOfBytes = bitSetSize / 8;
-        if (bitSetSize % 8 != 0) {
-            numOfBytes++;
+    /**
+     * Flush the data written to the underlying {@link java.io.OutputStream}. Any unfinished bytes
+     * will be padded with 0.
+     */
+    public void flush() throws IOException {
+        int endByte = mNextBitIndex / BYTE_BITS;
+        if (mNextBitIndex % BYTE_BITS != 0) {
+            // If next bit is not the first bit of a byte, then mNextBitIndex / BYTE_BITS would be
+            // the byte that includes already written bits. We need to increment it so this byte
+            // gets written.
+            endByte++;
         }
-        byte[] bytes = new byte[numOfBytes];
-        for (int i = 0; i < mBitSet.length(); i++) {
-            if (mBitSet.get(i)) {
-                bytes[i / 8] |= 1 << (7 - (i % 8));
-            }
-        }
-        return bytes;
+        mOutputStream.write(mBuffer, 0, endByte);
+        reset();
     }
 
-    /** Clear the stream. */
-    public void clear() {
-        mBitSet.clear();
-        mIndex = 0;
+    /** Reset this output stream to start state. */
+    private void reset() {
+        mNextBitIndex = 0;
+        Arrays.fill(mBuffer, (byte) 0);
     }
 }
diff --git a/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java b/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java
index f575599..0d6807a 100644
--- a/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java
+++ b/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java
@@ -23,31 +23,44 @@
  * An output stream that tracks the total number written bytes since construction and allows
  * querying this value any time during the execution.
  *
- * This class is used for constructing the rule indexing.
+ * <p>This class is used for constructing the rule indexing.
  */
-public class ByteTrackedOutputStream {
+public class ByteTrackedOutputStream extends OutputStream {
 
-    private static int sWrittenBytesCount;
-    private static OutputStream sOutputStream;
+    private static final int INT_BYTES = 4;
+
+    private int mWrittenBytesCount;
+    private final OutputStream mOutputStream;
 
     public ByteTrackedOutputStream(OutputStream outputStream) {
-        sWrittenBytesCount = 0;
-        sOutputStream = outputStream;
+        mWrittenBytesCount = 0;
+        mOutputStream = outputStream;
+    }
+
+    @Override
+    public void write(int b) throws IOException {
+        mWrittenBytesCount += INT_BYTES;
+        mOutputStream.write(b);
     }
 
     /**
-     * Writes the given bytes into the output stream provided in constructor and updates the
-     * total number of written bytes.
+     * Writes the given bytes into the output stream provided in constructor and updates the total
+     * number of written bytes.
      */
+    @Override
     public void write(byte[] bytes) throws IOException {
-        sWrittenBytesCount += bytes.length;
-        sOutputStream.write(bytes);
+        mWrittenBytesCount += bytes.length;
+        mOutputStream.write(bytes);
     }
 
-    /**
-     * Returns the total number of bytes written into the output stream at the requested time.
-     */
+    @Override
+    public void write(byte[] b, int off, int len) throws IOException {
+        mWrittenBytesCount += len;
+        mOutputStream.write(b, off, len);
+    }
+
+    /** Returns the total number of bytes written into the output stream at the requested time. */
     public int getWrittenBytesCount() {
-        return sWrittenBytesCount;
+        return mWrittenBytesCount;
     }
 }
diff --git a/services/core/java/com/android/server/integrity/model/IndexingFileConstants.java b/services/core/java/com/android/server/integrity/model/IndexingFileConstants.java
index 52df89870..d21febb 100644
--- a/services/core/java/com/android/server/integrity/model/IndexingFileConstants.java
+++ b/services/core/java/com/android/server/integrity/model/IndexingFileConstants.java
@@ -18,9 +18,9 @@
 
 /**  A helper class containing special indexing file constants. */
 public final class IndexingFileConstants {
-    // The parsing time seems acceptable for this block size based on the tests in
-    // go/ic-rule-file-format.
-    public static final int INDEXING_BLOCK_SIZE = 100;
+    // We empirically experimented with different block sizes and identified that 250 is in the
+    // optimal range of efficient computation.
+    public static final int INDEXING_BLOCK_SIZE = 250;
 
     public static final String START_INDEXING_KEY = "START_KEY";
     public static final String END_INDEXING_KEY = "END_KEY";
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
index 8f53be7..6afadba 100644
--- a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
+++ b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
@@ -97,42 +97,38 @@
                             ruleFileByteTrackedOutputStream);
             LinkedHashMap<String, Integer> unindexedRulesIndexes =
                     serializeRuleList(
-                            indexedRules.get(NOT_INDEXED),
-                            ruleFileByteTrackedOutputStream);
+                            indexedRules.get(NOT_INDEXED), ruleFileByteTrackedOutputStream);
 
             // Serialize their indexes.
-            BitOutputStream indexingBitOutputStream = new BitOutputStream();
-            serializeIndexGroup(packageNameIndexes, indexingBitOutputStream, /* isIndexed= */true);
-            serializeIndexGroup(appCertificateIndexes, indexingBitOutputStream, /* isIndexed= */
-                    true);
-            serializeIndexGroup(unindexedRulesIndexes, indexingBitOutputStream, /* isIndexed= */
-                    false);
-            // TODO(b/147609625): This dummy bit is set for fixing the padding issue. Remove when
-            // the issue is fixed and correct the tests that does this padding too.
-            indexingBitOutputStream.setNext();
-            indexingFileOutputStream.write(indexingBitOutputStream.toByteArray());
+            BitOutputStream indexingBitOutputStream = new BitOutputStream(indexingFileOutputStream);
+            serializeIndexGroup(packageNameIndexes, indexingBitOutputStream, /* isIndexed= */ true);
+            serializeIndexGroup(
+                    appCertificateIndexes, indexingBitOutputStream, /* isIndexed= */ true);
+            serializeIndexGroup(
+                    unindexedRulesIndexes, indexingBitOutputStream, /* isIndexed= */ false);
+            indexingBitOutputStream.flush();
         } catch (Exception e) {
             throw new RuleSerializeException(e.getMessage(), e);
         }
     }
 
-    private void serializeRuleFileMetadata(Optional<Integer> formatVersion,
-            ByteTrackedOutputStream outputStream)
+    private void serializeRuleFileMetadata(
+            Optional<Integer> formatVersion, ByteTrackedOutputStream outputStream)
             throws IOException {
         int formatVersionValue = formatVersion.orElse(DEFAULT_FORMAT_VERSION);
 
-        BitOutputStream bitOutputStream = new BitOutputStream();
+        BitOutputStream bitOutputStream = new BitOutputStream(outputStream);
         bitOutputStream.setNext(FORMAT_VERSION_BITS, formatVersionValue);
-        outputStream.write(bitOutputStream.toByteArray());
+        bitOutputStream.flush();
     }
 
     private LinkedHashMap<String, Integer> serializeRuleList(
             Map<String, List<Rule>> rulesMap, ByteTrackedOutputStream outputStream)
             throws IOException {
-        Preconditions.checkArgument(rulesMap != null,
-                "serializeRuleList should never be called with null rule list.");
+        Preconditions.checkArgument(
+                rulesMap != null, "serializeRuleList should never be called with null rule list.");
 
-        BitOutputStream bitOutputStream = new BitOutputStream();
+        BitOutputStream bitOutputStream = new BitOutputStream(outputStream);
         LinkedHashMap<String, Integer> indexMapping = new LinkedHashMap();
         indexMapping.put(START_INDEXING_KEY, outputStream.getWrittenBytesCount());
 
@@ -145,9 +141,8 @@
             }
 
             for (Rule rule : rulesMap.get(key)) {
-                bitOutputStream.clear();
                 serializeRule(rule, bitOutputStream);
-                outputStream.write(bitOutputStream.toByteArray());
+                bitOutputStream.flush();
                 indexTracker++;
             }
         }
@@ -156,7 +151,7 @@
         return indexMapping;
     }
 
-    private void serializeRule(Rule rule, BitOutputStream bitOutputStream) {
+    private void serializeRule(Rule rule, BitOutputStream bitOutputStream) throws IOException {
         if (rule == null) {
             throw new IllegalArgumentException("Null rule can not be serialized");
         }
@@ -171,7 +166,8 @@
         bitOutputStream.setNext();
     }
 
-    private void serializeFormula(Formula formula, BitOutputStream bitOutputStream) {
+    private void serializeFormula(Formula formula, BitOutputStream bitOutputStream)
+            throws IOException {
         if (formula instanceof AtomicFormula) {
             serializeAtomicFormula((AtomicFormula) formula, bitOutputStream);
         } else if (formula instanceof CompoundFormula) {
@@ -183,7 +179,7 @@
     }
 
     private void serializeCompoundFormula(
-            CompoundFormula compoundFormula, BitOutputStream bitOutputStream) {
+            CompoundFormula compoundFormula, BitOutputStream bitOutputStream) throws IOException {
         if (compoundFormula == null) {
             throw new IllegalArgumentException("Null compound formula can not be serialized");
         }
@@ -197,7 +193,7 @@
     }
 
     private void serializeAtomicFormula(
-            AtomicFormula atomicFormula, BitOutputStream bitOutputStream) {
+            AtomicFormula atomicFormula, BitOutputStream bitOutputStream) throws IOException {
         if (atomicFormula == null) {
             throw new IllegalArgumentException("Null atomic formula can not be serialized");
         }
@@ -231,11 +227,10 @@
     private void serializeIndexGroup(
             LinkedHashMap<String, Integer> indexes,
             BitOutputStream bitOutputStream,
-            boolean isIndexed) {
-
+            boolean isIndexed)
+            throws IOException {
         // Output the starting location of this indexing group.
-        serializeStringValue(
-                START_INDEXING_KEY, /* isHashedValue= */false, bitOutputStream);
+        serializeStringValue(START_INDEXING_KEY, /* isHashedValue= */ false, bitOutputStream);
         serializeIntValue(indexes.get(START_INDEXING_KEY), bitOutputStream);
 
         // If the group is indexed, output the locations of the indexes.
@@ -243,8 +238,8 @@
             for (Map.Entry<String, Integer> entry : indexes.entrySet()) {
                 if (!entry.getKey().equals(START_INDEXING_KEY)
                         && !entry.getKey().equals(END_INDEXING_KEY)) {
-                    serializeStringValue(entry.getKey(), /* isHashedValue= */false,
-                            bitOutputStream);
+                    serializeStringValue(
+                            entry.getKey(), /* isHashedValue= */ false, bitOutputStream);
                     serializeIntValue(entry.getValue(), bitOutputStream);
                 }
             }
@@ -256,7 +251,8 @@
     }
 
     private void serializeStringValue(
-            String value, boolean isHashedValue, BitOutputStream bitOutputStream) {
+            String value, boolean isHashedValue, BitOutputStream bitOutputStream)
+            throws IOException {
         if (value == null) {
             throw new IllegalArgumentException("String value can not be null.");
         }
@@ -269,11 +265,12 @@
         }
     }
 
-    private void serializeIntValue(int value, BitOutputStream bitOutputStream) {
+    private void serializeIntValue(int value, BitOutputStream bitOutputStream) throws IOException {
         bitOutputStream.setNext(/* numOfBits= */ 32, value);
     }
 
-    private void serializeBooleanValue(boolean value, BitOutputStream bitOutputStream) {
+    private void serializeBooleanValue(boolean value, BitOutputStream bitOutputStream)
+            throws IOException {
         bitOutputStream.setNext(value);
     }
 
diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
index 5191833..b67335a 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteProvider.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
@@ -306,14 +306,17 @@
                 }
             } else if (state == BluetoothProfile.STATE_DISCONNECTING
                     || state == BluetoothProfile.STATE_DISCONNECTED) {
-                btRoute.connectedProfiles.delete(profile);
-                if (btRoute.connectedProfiles.size() == 0) {
-                    mBluetoothRoutes.remove(device.getAddress());
-                    if (mActiveDevice != null
-                            && TextUtils.equals(mActiveDevice.getAddress(), device.getAddress())) {
-                        mActiveDevice = null;
+                if (btRoute != null) {
+                    btRoute.connectedProfiles.delete(profile);
+                    if (btRoute.connectedProfiles.size() == 0) {
+                        mBluetoothRoutes.remove(device.getAddress());
+                        if (mActiveDevice != null
+                                && TextUtils.equals(mActiveDevice.getAddress(),
+                                device.getAddress())) {
+                            mActiveDevice = null;
+                        }
+                        notifyBluetoothRoutesUpdated();
                     }
-                    notifyBluetoothRoutesUpdated();
                 }
             }
         }
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index df115d0..1905571 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -135,6 +135,7 @@
     private int mMaxVolume = 0;
     private int mCurrentVolume = 0;
     private int mOptimisticVolume = -1;
+    private String mVolumeControlId;
     // End volume handling fields
 
     private boolean mIsActive = false;
@@ -714,7 +715,7 @@
             if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
                 int current = mOptimisticVolume != -1 ? mOptimisticVolume : mCurrentVolume;
                 return new PlaybackInfo(mVolumeType, mVolumeControlType, mMaxVolume, current,
-                        mAudioAttrs);
+                        mAudioAttrs, mVolumeControlId);
             }
             volumeType = mVolumeType;
             attributes = mAudioAttrs;
@@ -723,7 +724,7 @@
         int max = mAudioManager.getStreamMaxVolume(stream);
         int current = mAudioManager.getStreamVolume(stream);
         return new PlaybackInfo(volumeType, VolumeProvider.VOLUME_CONTROL_ABSOLUTE, max,
-                current, attributes);
+                current, attributes, null);
     }
 
     private final Runnable mClearOptimisticVolumeRunnable = new Runnable() {
@@ -886,6 +887,7 @@
             synchronized (mLock) {
                 typeChanged = mVolumeType == PlaybackInfo.PLAYBACK_TYPE_REMOTE;
                 mVolumeType = PlaybackInfo.PLAYBACK_TYPE_LOCAL;
+                mVolumeControlId = null;
                 if (attributes != null) {
                     mAudioAttrs = attributes;
                 } else {
@@ -904,13 +906,15 @@
         }
 
         @Override
-        public void setPlaybackToRemote(int control, int max) throws RemoteException {
+        public void setPlaybackToRemote(int control, int max, String controlId)
+                throws RemoteException {
             boolean typeChanged;
             synchronized (mLock) {
                 typeChanged = mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL;
                 mVolumeType = PlaybackInfo.PLAYBACK_TYPE_REMOTE;
                 mVolumeControlType = control;
                 mMaxVolume = max;
+                mVolumeControlId = controlId;
             }
             if (typeChanged) {
                 final long token = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index d8a4655..c518614 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -161,7 +161,7 @@
 import android.net.NetworkState;
 import android.net.NetworkStats;
 import android.net.NetworkTemplate;
-import android.net.StringNetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
 import android.net.TrafficStats;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
@@ -2877,17 +2877,6 @@
     }
 
     @Override
-    public void onTetheringChanged(String iface, boolean tethering) {
-        // No need to enforce permission because setRestrictBackground() will do it.
-        synchronized (mUidRulesFirstLock) {
-            if (mRestrictBackground && tethering) {
-                Log.d(TAG, "Tethering on (" + iface +"); disable Data Saver");
-                setRestrictBackground(false);
-            }
-        }
-    }
-
-    @Override
     public void setRestrictBackground(boolean restrictBackground) {
         Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "setRestrictBackground");
         try {
@@ -4522,7 +4511,11 @@
                 }
                 case MSG_STATS_PROVIDER_LIMIT_REACHED: {
                     mNetworkStats.forceUpdate();
+
                     synchronized (mNetworkPoliciesSecondLock) {
+                        // Some providers might hit the limit reached event prior to others. Thus,
+                        // re-calculate and update interface quota for every provider is needed.
+                        updateNetworkRulesNL();
                         updateNetworkEnabledNL();
                         updateNotificationsNL();
                     }
@@ -4530,16 +4523,21 @@
                 }
                 case MSG_LIMIT_REACHED: {
                     final String iface = (String) msg.obj;
+                    synchronized (mNetworkPoliciesSecondLock) {
+                        // fast return if not needed.
+                        if (!mMeteredIfaces.contains(iface)) {
+                            return true;
+                        }
+                    }
+
+                    // force stats update to make sure the service have the numbers that caused
+                    // alert to trigger.
+                    mNetworkStats.forceUpdate();
 
                     synchronized (mNetworkPoliciesSecondLock) {
-                        if (mMeteredIfaces.contains(iface)) {
-                            // force stats update to make sure we have
-                            // numbers that caused alert to trigger.
-                            mNetworkStats.forceUpdate();
-
-                            updateNetworkEnabledNL();
-                            updateNotificationsNL();
-                        }
+                        updateNetworkRulesNL();
+                        updateNetworkEnabledNL();
+                        updateNotificationsNL();
                     }
                     return true;
                 }
@@ -5283,16 +5281,12 @@
     }
 
     private int parseSubId(NetworkState state) {
-        // TODO: moved to using a legitimate NetworkSpecifier instead of string parsing
         int subId = INVALID_SUBSCRIPTION_ID;
         if (state != null && state.networkCapabilities != null
                 && state.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
             NetworkSpecifier spec = state.networkCapabilities.getNetworkSpecifier();
-            if (spec instanceof StringNetworkSpecifier) {
-                try {
-                    subId = Integer.parseInt(((StringNetworkSpecifier) spec).specifier);
-                } catch (NumberFormatException e) {
-                }
+            if (spec instanceof TelephonyNetworkSpecifier) {
+                subId = ((TelephonyNetworkSpecifier) spec).getSubscriptionId();
             }
         }
         return subId;
diff --git a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
index da2ca9b..eaf120e 100644
--- a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
@@ -42,9 +42,10 @@
             return null;
         }
 
-        record.updateNotificationChannel(mConfig.getNotificationChannel(record.sbn.getPackageName(),
+        record.updateNotificationChannel(mConfig.getConversationNotificationChannel(
+                record.sbn.getPackageName(),
                 record.sbn.getUid(), record.getChannel().getId(),
-                record.getNotification().getShortcutId(), false));
+                record.getNotification().getShortcutId(), true, false));
 
         return null;
     }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 0e08033..1d49364 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -472,7 +472,8 @@
     private static final String LOCKSCREEN_ALLOW_SECURE_NOTIFICATIONS_VALUE = "value";
 
     private RankingHelper mRankingHelper;
-    private PreferencesHelper mPreferencesHelper;
+    @VisibleForTesting
+    PreferencesHelper mPreferencesHelper;
 
     private final UserProfiles mUserProfiles = new UserProfiles();
     private NotificationListeners mListeners;
@@ -3054,23 +3055,24 @@
         public NotificationChannel getNotificationChannel(String callingPkg, int userId,
                 String targetPkg, String channelId) {
             return getConversationNotificationChannel(
-                    callingPkg, userId, targetPkg, channelId, null);
+                    callingPkg, userId, targetPkg, channelId, true, null);
         }
 
         @Override
         public NotificationChannel getConversationNotificationChannel(String callingPkg, int userId,
-                String targetPkg, String channelId, String conversationId) {
+                String targetPkg, String channelId, boolean returnParentIfNoConversationChannel,
+                String conversationId) {
             if (canNotifyAsPackage(callingPkg, targetPkg, userId)
-                    || isCallingUidSystem()) {
+                    || isCallerIsSystemOrSystemUi()) {
                 int targetUid = -1;
                 try {
                     targetUid = mPackageManagerClient.getPackageUidAsUser(targetPkg, userId);
                 } catch (NameNotFoundException e) {
                     /* ignore */
                 }
-                return mPreferencesHelper.getNotificationChannel(
+                return mPreferencesHelper.getConversationNotificationChannel(
                         targetPkg, targetUid, channelId, conversationId,
-                        false /* includeDeleted */);
+                        returnParentIfNoConversationChannel, false /* includeDeleted */);
             }
             throw new SecurityException("Pkg " + callingPkg
                     + " cannot read channels for " + targetPkg + " in " + userId);
@@ -5255,9 +5257,15 @@
         if (mIsTelevision && (new Notification.TvExtender(notification)).getChannelId() != null) {
             channelId = (new Notification.TvExtender(notification)).getChannelId();
         }
-        final NotificationChannel channel = mPreferencesHelper.getNotificationChannel(pkg,
-                notificationUid, channelId, notification.getShortcutId(),
-                false /* includeDeleted */);
+        // TODO: flag this behavior
+        String shortcutId = notification.getShortcutId();
+        if (shortcutId == null
+            && notification.getNotificationStyle() == Notification.MessagingStyle.class) {
+            shortcutId = id + tag + NotificationChannel.PLACEHOLDER_CONVERSATION_ID;
+        }
+        final NotificationChannel channel = mPreferencesHelper.getConversationNotificationChannel(
+                pkg, notificationUid, channelId, shortcutId,
+                true /* parent ok */, false /* includeDeleted */);
         if (channel == null) {
             final String noChannelStr = "No Channel found for "
                     + "pkg=" + pkg
@@ -7893,6 +7901,14 @@
         return isUidSystemOrPhone(Binder.getCallingUid());
     }
 
+    private boolean isCallerIsSystemOrSystemUi() {
+        if (isCallerSystemOrPhone()) {
+            return true;
+        }
+        return getContext().checkCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
+                == PERMISSION_GRANTED;
+    }
+
     private void checkCallerIsSystemOrShell() {
         int callingUid = Binder.getCallingUid();
         if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) {
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 92fcb7f..185d75c 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -858,12 +858,13 @@
     public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
             boolean includeDeleted) {
         Objects.requireNonNull(pkg);
-        return getNotificationChannel(pkg, uid, channelId, null, includeDeleted);
+        return getConversationNotificationChannel(pkg, uid, channelId, null, true, includeDeleted);
     }
 
     @Override
-    public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
-            String conversationId, boolean includeDeleted) {
+    public NotificationChannel getConversationNotificationChannel(String pkg, int uid,
+            String channelId, String conversationId, boolean returnParentIfNoConversationChannel,
+            boolean includeDeleted) {
         Preconditions.checkNotNull(pkg);
         synchronized (mPackagePreferences) {
             PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
@@ -873,16 +874,19 @@
             if (channelId == null) {
                 channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
             }
-            if (conversationId == null) {
+            NotificationChannel channel = null;
+            if (conversationId != null) {
+                // look for an automatically created conversation specific channel
+                channel = findConversationChannel(r, channelId, conversationId, includeDeleted);
+            }
+            if (channel == null && returnParentIfNoConversationChannel) {
+                // look for it just based on its id
                 final NotificationChannel nc = r.channels.get(channelId);
                 if (nc != null && (includeDeleted || !nc.isDeleted())) {
                     return nc;
                 }
-            } else {
-                // look for an automatically created conversation specific channel
-                return findConversationChannel(r, channelId, conversationId, includeDeleted);
             }
-            return null;
+            return channel;
         }
     }
 
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index 4b044c1..7e98be7 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -45,8 +45,9 @@
             boolean fromUser);
     NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
             boolean includeDeleted);
-    NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
-            String conversationId, boolean includeDeleted);
+    NotificationChannel getConversationNotificationChannel(String pkg, int uid, String channelId,
+            String conversationId, boolean returnParentIfNoConversationChannel,
+            boolean includeDeleted);
     void deleteNotificationChannel(String pkg, int uid, String channelId);
     void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId);
     void permanentlyDeleteNotificationChannels(String pkg, int uid);
diff --git a/services/core/java/com/android/server/people/PeopleServiceInternal.java b/services/core/java/com/android/server/people/PeopleServiceInternal.java
index 31d30362..c5b868f 100644
--- a/services/core/java/com/android/server/people/PeopleServiceInternal.java
+++ b/services/core/java/com/android/server/people/PeopleServiceInternal.java
@@ -16,9 +16,25 @@
 
 package com.android.server.people;
 
+import android.annotation.NonNull;
 import android.service.appprediction.IPredictionService;
 
 /**
  * @hide Only for use within the system server.
  */
-public abstract class PeopleServiceInternal extends IPredictionService.Stub {}
+public abstract class PeopleServiceInternal extends IPredictionService.Stub {
+
+    /**
+     * The number conversation infos will be dynamic, based on the currently installed apps on the
+     * device. All of which should be combined into a single blob to be backed up.
+     */
+    public abstract byte[] backupConversationInfos(@NonNull int userId);
+
+    /**
+     * Multiple conversation infos may exist in the restore payload, child classes are required to
+     * manage the restoration based on how individual conversation infos were originally combined
+     * during backup.
+     */
+    public abstract void restoreConversationInfos(@NonNull int userId, @NonNull String key,
+            @NonNull byte[] payload);
+}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 159b4e4..410017a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -190,6 +190,7 @@
 import android.content.pm.ServiceInfo;
 import android.content.pm.SharedLibraryInfo;
 import android.content.pm.Signature;
+import android.content.pm.SigningInfo;
 import android.content.pm.SuspendDialogInfo;
 import android.content.pm.UserInfo;
 import android.content.pm.VerifierDeviceIdentity;
@@ -20021,8 +20022,9 @@
         String initiatingPackageName;
         String originatingPackageName;
 
+        final InstallSource installSource;
         synchronized (mLock) {
-            final InstallSource installSource = getInstallSourceLocked(packageName, callingUid);
+            installSource = getInstallSourceLocked(packageName, callingUid);
             if (installSource == null) {
                 return null;
             }
@@ -20036,9 +20038,16 @@
             }
 
             if (installSource.isInitiatingPackageUninstalled) {
-                // TODO(b/146555198) Allow the app itself to see the info
-                // (at least for non-instant apps)
-                initiatingPackageName = null;
+                // We can't check visibility in the usual way, since the initiating package is no
+                // longer present. So we apply simpler rules to whether to expose the info:
+                // 1. Instant apps can't see it.
+                // 2. Otherwise only the installed app itself can see it.
+                final boolean isInstantApp = getInstantAppPackageName(callingUid) != null;
+                if (!isInstantApp && isCallerSameApp(packageName, callingUid)) {
+                    initiatingPackageName = installSource.initiatingPackageName;
+                } else {
+                    initiatingPackageName = null;
+                }
             } else {
                 // All installSource strings are interned, so == is ok here
                 if (installSource.initiatingPackageName == installSource.installerPackageName) {
@@ -20063,13 +20072,27 @@
             }
         }
 
+        // Remaining work can safely be done outside the lock. (Note that installSource is
+        // immutable so it's ok to carry on reading from it.)
+
         if (originatingPackageName != null && mContext.checkCallingOrSelfPermission(
                 Manifest.permission.INSTALL_PACKAGES) != PackageManager.PERMISSION_GRANTED) {
             originatingPackageName = null;
         }
 
-        return new InstallSourceInfo(initiatingPackageName, originatingPackageName,
-                installerPackageName);
+        // If you can see the initiatingPackageName, and we have valid signing info for it,
+        // then we let you see that too.
+        final SigningInfo initiatingPackageSigningInfo;
+        final PackageSignatures signatures = installSource.initiatingPackageSignatures;
+        if (initiatingPackageName != null && signatures != null
+                && signatures.mSigningDetails != SigningDetails.UNKNOWN) {
+            initiatingPackageSigningInfo = new SigningInfo(signatures.mSigningDetails);
+        } else {
+            initiatingPackageSigningInfo = null;
+        }
+
+        return new InstallSourceInfo(initiatingPackageName, initiatingPackageSigningInfo,
+                originatingPackageName, installerPackageName);
     }
 
     @GuardedBy("mLock")
@@ -22267,6 +22290,13 @@
         mPermissionManager.onNewUserCreated(userId);
     }
 
+    boolean readPermissionStateForUser(@UserIdInt int userId) {
+        synchronized (mPackages) {
+            mSettings.readPermissionStateForUserSyncLPr(userId);
+            return mSettings.areDefaultRuntimePermissionsGrantedLPr(userId);
+        }
+    }
+
     @Override
     public VerifierDeviceIdentity getVerifierDeviceIdentity() throws RemoteException {
         mContext.enforceCallingOrSelfPermission(
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 3e665c3..4f18cb4 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -3125,6 +3125,10 @@
         return true;
     }
 
+    void readPermissionStateForUserSyncLPr(@UserIdInt int userId) {
+        mRuntimePermissionsPersistence.readStateForUserSyncLPr(userId);
+    }
+
     void applyDefaultPreferredAppsLPw(int userId) {
         // First pull data from any pre-installed apps.
         final PackageManagerInternal pmInternal =
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 2265d01..7888d1f 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -49,6 +49,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.ParcelFileDescriptor;
+import android.os.ParcelableException;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -570,18 +571,17 @@
         } else {
             params.installFlags |= PackageManager.INSTALL_DISABLE_VERIFICATION;
         }
-        int apkSessionId = mPi.createSession(
-                params, originalSession.getInstallerPackageName(),
-                0 /* UserHandle.SYSTEM */);
-        PackageInstallerSession apkSession = mPi.getSession(apkSessionId);
-
         try {
+            int apkSessionId = mPi.createSession(
+                    params, originalSession.getInstallerPackageName(),
+                    0 /* UserHandle.SYSTEM */);
+            PackageInstallerSession apkSession = mPi.getSession(apkSessionId);
             apkSession.open();
             for (String apkFilePath : apkFilePaths) {
                 File apkFile = new File(apkFilePath);
                 ParcelFileDescriptor pfd = ParcelFileDescriptor.open(apkFile,
                         ParcelFileDescriptor.MODE_READ_ONLY);
-                long sizeBytes = pfd.getStatSize();
+                long sizeBytes = (pfd == null) ? -1 : pfd.getStatSize();
                 if (sizeBytes < 0) {
                     Slog.e(TAG, "Unable to get size of: " + apkFilePath);
                     throw new PackageManagerException(errorCode,
@@ -589,11 +589,11 @@
                 }
                 apkSession.write(apkFile.getName(), 0, sizeBytes, pfd);
             }
-        } catch (IOException e) {
+            return apkSession;
+        } catch (IOException | ParcelableException e) {
             Slog.e(TAG, "Failure to install APK staged session " + originalSession.sessionId, e);
-            throw new PackageManagerException(errorCode, "Failed to write APK session", e);
+            throw new PackageManagerException(errorCode, "Failed to create/write APK session", e);
         }
-        return apkSession;
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index e5d5b57..6ee8648 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -492,6 +492,10 @@
         public void onBootPhase(int phase) {
             if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
                 mUms.cleanupPartialUsers();
+
+                if (mUms.mPm.isDeviceUpgrading()) {
+                    mUms.cleanupPreCreatedUsers();
+                }
             }
         }
 
@@ -617,6 +621,33 @@
         }
     }
 
+    /**
+     * Removes any pre-created users from the system. Should be invoked after OTAs, to ensure
+     * pre-created users are not stale. New pre-created pool can be re-created after the update.
+     */
+    void cleanupPreCreatedUsers() {
+        final ArrayList<UserInfo> preCreatedUsers;
+        synchronized (mUsersLock) {
+            final int userSize = mUsers.size();
+            preCreatedUsers = new ArrayList<>(userSize);
+            for (int i = 0; i < userSize; i++) {
+                UserInfo ui = mUsers.valueAt(i).info;
+                if (ui.preCreated) {
+                    preCreatedUsers.add(ui);
+                    addRemovingUserIdLocked(ui.id);
+                    ui.flags |= UserInfo.FLAG_DISABLED;
+                    ui.partial = true;
+                }
+            }
+        }
+        final int preCreatedSize = preCreatedUsers.size();
+        for (int i = 0; i < preCreatedSize; i++) {
+            UserInfo ui = preCreatedUsers.get(i);
+            Slog.i(LOG_TAG, "Removing pre-created user " + ui.id);
+            removeUserState(ui.id);
+        }
+    }
+
     @Override
     public String getUserAccount(@UserIdInt int userId) {
         checkManageUserAndAcrossUsersFullPermission("get user account");
@@ -3078,7 +3109,6 @@
             @NonNull String userType, @UserInfoFlag int flags, @UserIdInt int parentId,
             boolean preCreate, @Nullable String[] disallowedPackages,
             @NonNull TimingsTraceAndSlog t) {
-
         final UserTypeDetails userTypeDetails = mUserTypes.get(userType);
         if (userTypeDetails == null) {
             Slog.e(LOG_TAG, "Cannot create user of invalid user type: " + userType);
@@ -3254,9 +3284,9 @@
                 mBaseUserRestrictions.append(userId, restrictions);
             }
 
-            t.traceBegin("PM.onNewUserCreated");
+            t.traceBegin("PM.onNewUserCreated-" + userId);
             mPm.onNewUserCreated(userId);
-
+            t.traceEnd();
             if (preCreate) {
                 // Must start user (which will be stopped right away, through
                 // UserController.finishUserUnlockedCompleted) so services can properly
@@ -3323,11 +3353,16 @@
         preCreatedUser.preCreated = false;
         preCreatedUser.creationTime = getCreationTime();
 
-        dispatchUserAddedIntent(preCreatedUser);
         synchronized (mPackagesLock) {
             writeUserLP(preCreatedUserData);
             writeUserListLP();
         }
+        updateUserIds();
+        if (!mPm.readPermissionStateForUser(preCreatedUser.id)) {
+            // Could not read the existing permissions, re-grant them.
+            mPm.onNewUserCreated(preCreatedUser.id);
+        }
+        dispatchUserAddedIntent(preCreatedUser);
         return preCreatedUser;
     }
 
@@ -4027,14 +4062,16 @@
         synchronized (mUsersLock) {
             final int userSize = mUsers.size();
             for (int i = 0; i < userSize; i++) {
-                if (!mUsers.valueAt(i).info.partial) {
+                UserInfo userInfo = mUsers.valueAt(i).info;
+                if (!userInfo.partial && !userInfo.preCreated) {
                     num++;
                 }
             }
             final int[] newUsers = new int[num];
             int n = 0;
             for (int i = 0; i < userSize; i++) {
-                if (!mUsers.valueAt(i).info.partial) {
+                UserInfo userInfo = mUsers.valueAt(i).info;
+                if (!userInfo.partial && !userInfo.preCreated) {
                     newUsers[n++] = mUsers.keyAt(i);
                 }
             }
@@ -4095,7 +4132,10 @@
      * recycled.
      */
     void reconcileUsers(String volumeUuid) {
-        mUserDataPreparer.reconcileUsers(volumeUuid, getUsers(true /* excludeDying */));
+        mUserDataPreparer.reconcileUsers(volumeUuid, getUsers(
+                /* excludePartial= */ true,
+                /* excludeDying= */ true,
+                /* excludePreCreated= */ false));
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 815f7b4..89030ed 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -199,13 +199,31 @@
     );
 
     /**
-     * Special user restrictions that are applied globally when set by the profile owner of a
-     * managed profile that was created during the device provisioning flow.
+     * Special user restrictions that profile owner of an organization-owned managed profile can
+     * set on the parent profile instance to apply them globally.
      */
     private static final Set<String> PROFILE_OWNER_ORGANIZATION_OWNED_GLOBAL_RESTRICTIONS =
             Sets.newArraySet(
                     UserManager.DISALLOW_CONFIG_DATE_TIME,
-                    UserManager.DISALLOW_CAMERA
+                    UserManager.DISALLOW_CAMERA,
+                    UserManager.DISALLOW_ADD_USER,
+                    UserManager.DISALLOW_BLUETOOTH,
+                    UserManager.DISALLOW_BLUETOOTH_SHARING,
+                    UserManager.DISALLOW_CONFIG_BLUETOOTH,
+                    UserManager.DISALLOW_CONFIG_CELL_BROADCASTS,
+                    UserManager.DISALLOW_CONFIG_LOCATION,
+                    UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS,
+                    UserManager.DISALLOW_CONFIG_PRIVATE_DNS,
+                    UserManager.DISALLOW_CONFIG_TETHERING,
+                    UserManager.DISALLOW_CONFIG_WIFI,
+                    UserManager.DISALLOW_CONTENT_CAPTURE,
+                    UserManager.DISALLOW_CONTENT_SUGGESTIONS,
+                    UserManager.DISALLOW_DATA_ROAMING,
+                    UserManager.DISALLOW_DEBUGGING_FEATURES,
+                    UserManager.DISALLOW_SAFE_BOOT,
+                    UserManager.DISALLOW_SHARE_LOCATION,
+                    UserManager.DISALLOW_SMS,
+                    UserManager.DISALLOW_USB_FILE_TRANSFER
     );
 
     /**
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index d468cd9..0411e29 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -55,6 +55,8 @@
 import android.app.ActivityManager;
 import android.app.ApplicationPackageManager;
 import android.app.IActivityManager;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
@@ -107,6 +109,7 @@
 import android.util.SparseBooleanArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.compat.IPlatformCompat;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.os.RoSystemProperties;
@@ -224,6 +227,8 @@
     private final Handler mHandler;
     private final Context mContext;
     private final MetricsLogger mMetricsLogger = new MetricsLogger();
+    private final IPlatformCompat mPlatformCompat = IPlatformCompat.Stub.asInterface(
+            ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
 
     /** Internal storage for permissions and related settings */
     @GuardedBy("mLock")
@@ -1824,6 +1829,14 @@
         return true;
     }
 
+    /**
+     * This change makes it so that apps are told to show rationale for asking for background
+     * location access every time they request.
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+    private static final long BACKGROUND_RATIONALE_CHANGE_ID = 147316723L;
+
     @Override
     public boolean shouldShowRequestPermissionRationale(String permName,
             String packageName, int userId) {
@@ -1862,6 +1875,16 @@
             return false;
         }
 
+        try {
+            if (permName.equals(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
+                    && mPlatformCompat.isChangeEnabledByPackageName(BACKGROUND_RATIONALE_CHANGE_ID,
+                    packageName, userId)) {
+                return true;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Unable to check if compatibility change is enabled.", e);
+        }
+
         return (flags & PackageManager.FLAG_PERMISSION_USER_SET) != 0;
     }
 
diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java
index 9f592b8..7d0072a 100644
--- a/services/core/java/com/android/server/rollback/Rollback.java
+++ b/services/core/java/com/android/server/rollback/Rollback.java
@@ -172,6 +172,13 @@
     @Nullable public final String mInstallerPackageName;
 
     /**
+     * This array holds all of the rollback tokens associated with package sessions included in
+     * this rollback.
+     */
+    @GuardedBy("mLock")
+    private final IntArray mTokens = new IntArray();
+
+    /**
      * Constructs a new, empty Rollback instance.
      *
      * @param rollbackId the id of the rollback.
@@ -766,6 +773,26 @@
         }
     }
 
+    /**
+     * Adds a rollback token to be associated with this rollback. This may be used to
+     * identify which rollback should be removed in case {@link PackageManager} sends an
+     * {@link Intent#ACTION_CANCEL_ENABLE_ROLLBACK} intent.
+     */
+    void addToken(int token) {
+        synchronized (mLock) {
+            mTokens.add(token);
+        }
+    }
+
+    /**
+     * Returns true if this rollback is associated with the provided {@code token}.
+     */
+    boolean hasToken(int token) {
+        synchronized (mLock) {
+            return mTokens.indexOf(token) != -1;
+        }
+    }
+
     static String rollbackStateToString(@RollbackState int state) {
         switch (state) {
             case Rollback.ROLLBACK_STATE_ENABLING: return "enabling";
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 8f8a5c4..eefcde6 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -235,12 +235,17 @@
                         Slog.v(TAG, "broadcast=ACTION_CANCEL_ENABLE_ROLLBACK token=" + token);
                     }
                     synchronized (mLock) {
-                        for (NewRollback rollback : mNewRollbacks) {
-                            if (rollback.hasToken(token)) {
-                                rollback.setCancelled();
-                                return;
+                        NewRollback found = null;
+                        for (NewRollback newRollback : mNewRollbacks) {
+                            if (newRollback.rollback.hasToken(token)) {
+                                found = newRollback;
+                                break;
                             }
                         }
+                        if (found != null) {
+                            mNewRollbacks.remove(found);
+                            found.rollback.delete(mAppDataRollbackHelper);
+                        }
                     }
                 }
             }
@@ -433,10 +438,14 @@
                     rollback.delete(mAppDataRollbackHelper);
                 }
             }
-            for (NewRollback newRollback : mNewRollbacks) {
+            Iterator<NewRollback> iter2 = mNewRollbacks.iterator();
+            while (iter2.hasNext()) {
+                NewRollback newRollback = iter2.next();
                 if (newRollback.rollback.includesPackage(packageName)) {
-                    newRollback.setCancelled();
+                    iter2.remove();
+                    newRollback.rollback.delete(mAppDataRollbackHelper);
                 }
+
             }
         }
     }
@@ -798,7 +807,7 @@
                 mNewRollbacks.add(newRollback);
             }
         }
-        newRollback.addToken(token);
+        newRollback.rollback.addToken(token);
 
         return enableRollbackForPackageSession(newRollback.rollback, packageSession);
     }
@@ -1220,12 +1229,6 @@
             Slog.v(TAG, "completeEnableRollback id=" + rollback.info.getRollbackId());
         }
 
-        if (newRollback.isCancelled()) {
-            Slog.e(TAG, "Rollback has been cancelled by PackageManager");
-            rollback.delete(mAppDataRollbackHelper);
-            return null;
-        }
-
         // We are checking if number of packages (excluding apk-in-apex) we enabled for rollback is
         // equal to the number of sessions we are installing, to ensure we didn't skip enabling
         // of any sessions. If we successfully enable an apex, then we can assume we enabled
@@ -1335,22 +1338,12 @@
         public final Rollback rollback;
 
         /**
-         * This array holds all of the rollback tokens associated with package sessions included in
-         * this rollback.
-         */
-        @GuardedBy("mNewRollbackLock")
-        private final IntArray mTokens = new IntArray();
-
-        /**
          * Session ids for all packages in the install. For multi-package sessions, this is the list
          * of child session ids. For normal sessions, this list is a single element with the normal
          * session id.
          */
         private final int[] mPackageSessionIds;
 
-        @GuardedBy("mNewRollbackLock")
-        private boolean mIsCancelled = false;
-
         /**
          * The number of sessions in the install which are notified with success by
          * {@link PackageInstaller.SessionCallback#onFinished(int, boolean)}.
@@ -1367,52 +1360,6 @@
         }
 
         /**
-         * Adds a rollback token to be associated with this NewRollback. This may be used to
-         * identify which rollback should be cancelled in case {@link PackageManager} sends an
-         * {@link Intent#ACTION_CANCEL_ENABLE_ROLLBACK} intent.
-         */
-        void addToken(int token) {
-            synchronized (mNewRollbackLock) {
-                mTokens.add(token);
-            }
-        }
-
-        /**
-         * Returns true if this NewRollback is associated with the provided {@code token}.
-         */
-        boolean hasToken(int token) {
-            synchronized (mNewRollbackLock) {
-                return mTokens.indexOf(token) != -1;
-            }
-        }
-
-        /**
-         * Returns true if this NewRollback has been cancelled.
-         *
-         * <p>Rollback could be invalidated and cancelled if RollbackManager receives
-         * {@link Intent#ACTION_CANCEL_ENABLE_ROLLBACK} from {@link PackageManager}.
-         *
-         * <p>The main underlying assumption here is that if enabling the rollback times out, then
-         * {@link PackageManager} will NOT send
-         * {@link PackageInstaller.SessionCallback#onFinished(int, boolean)} before it broadcasts
-         * {@link Intent#ACTION_CANCEL_ENABLE_ROLLBACK}.
-         */
-        boolean isCancelled() {
-            synchronized (mNewRollbackLock) {
-                return mIsCancelled;
-            }
-        }
-
-        /**
-         * Sets this NewRollback to be marked as cancelled.
-         */
-        void setCancelled() {
-            synchronized (mNewRollbackLock) {
-                mIsCancelled = true;
-            }
-        }
-
-        /**
          * Returns true if this NewRollback contains the provided {@code packageSessionId}.
          */
         boolean containsSessionId(int packageSessionId) {
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index a0ef8cf..b9ef7b3 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -280,7 +280,6 @@
                             .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED,
                             WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN,
                             "");
-                    mContext.getSystemService(PowerManager.class).reboot("Rollback staged install");
                 } else if (sessionInfo.isStagedSessionFailed()
                         && markStagedSessionHandled(rollbackId)) {
                     logEvent(moduleMetadataPackage,
@@ -291,6 +290,11 @@
                 }
             }
         }
+
+        // Wait for all pending staged sessions to get handled before rebooting.
+        if (isPendingStagedSessionsEmpty()) {
+            mContext.getSystemService(PowerManager.class).reboot("Rollback staged install");
+        }
     }
 
     /**
@@ -303,6 +307,16 @@
         }
     }
 
+    /**
+     * Returns {@code true} if all pending staged rollback sessions were marked as handled,
+     * {@code false} if there is any left.
+     */
+    private boolean isPendingStagedSessionsEmpty() {
+        synchronized (mPendingStagedRollbackIds) {
+            return mPendingStagedRollbackIds.isEmpty();
+        }
+    }
+
     private void saveLastStagedRollbackId(int stagedRollbackId) {
         try {
             FileOutputStream fos = new FileOutputStream(mLastStagedRollbackIdFile);
@@ -414,6 +428,9 @@
                             reasonToLog, failedPackageToLog);
                 }
             } else {
+                if (rollback.isStaged()) {
+                    markStagedSessionHandled(rollback.getRollbackId());
+                }
                 logEvent(logPackage,
                         StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE,
                         reasonToLog, failedPackageToLog);
@@ -431,6 +448,16 @@
         RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
         List<RollbackInfo> rollbacks = rollbackManager.getAvailableRollbacks();
 
+        // Add all rollback ids to mPendingStagedRollbackIds, so that we do not reboot before all
+        // pending staged rollbacks are handled.
+        synchronized (mPendingStagedRollbackIds) {
+            for (RollbackInfo rollback : rollbacks) {
+                if (rollback.isStaged()) {
+                    mPendingStagedRollbackIds.add(rollback.getRollbackId());
+                }
+            }
+        }
+
         for (RollbackInfo rollback : rollbacks) {
             VersionedPackage sample = rollback.getPackages().get(0).getVersionRolledBackFrom();
             rollbackPackage(rollback, sample, PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/HalFactory.java b/services/core/java/com/android/server/soundtrigger_middleware/HalFactory.java
new file mode 100644
index 0000000..b19e2ed
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/HalFactory.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.soundtrigger_middleware;
+
+import android.hardware.soundtrigger.V2_0.ISoundTriggerHw;
+
+/**
+ * A factory for creating instances of {@link ISoundTriggerHw}.
+ *
+ * @hide
+ */
+public interface HalFactory {
+    /**
+     * @return An instance of {@link ISoundTriggerHw}.
+     */
+    ISoundTriggerHw create();
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
index 9d51b65..9f4b09a 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
@@ -78,18 +78,17 @@
     }
 
     /**
-     * Most generic constructor - gets an array of HAL driver instances.
+     * Constructor - gets an array of HAL driver factories.
      */
-    public SoundTriggerMiddlewareImpl(@NonNull ISoundTriggerHw[] halServices,
+    public SoundTriggerMiddlewareImpl(@NonNull HalFactory[] halFactories,
             @NonNull AudioSessionProvider audioSessionProvider) {
-        List<SoundTriggerModule> modules = new ArrayList<>(halServices.length);
+        List<SoundTriggerModule> modules = new ArrayList<>(halFactories.length);
 
-        for (int i = 0; i < halServices.length; ++i) {
-            ISoundTriggerHw service = halServices[i];
+        for (int i = 0; i < halFactories.length; ++i) {
             try {
-                modules.add(new SoundTriggerModule(service, audioSessionProvider));
+                modules.add(new SoundTriggerModule(halFactories[i], audioSessionProvider));
             } catch (Exception e) {
-                Log.e(TAG, "Failed to a SoundTriggerModule instance", e);
+                Log.e(TAG, "Failed to add a SoundTriggerModule instance", e);
             }
         }
 
@@ -97,11 +96,11 @@
     }
 
     /**
-     * Convenience constructor - gets a single HAL driver instance.
+     * Convenience constructor - gets a single HAL factory.
      */
-    public SoundTriggerMiddlewareImpl(@NonNull ISoundTriggerHw halService,
+    public SoundTriggerMiddlewareImpl(@NonNull HalFactory factory,
             @NonNull AudioSessionProvider audioSessionProvider) {
-        this(new ISoundTriggerHw[]{halService}, audioSessionProvider);
+        this(new HalFactory[]{factory}, audioSessionProvider);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
index 5a06a2c..12f9fd9 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
@@ -113,9 +113,9 @@
 public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareService.Stub {
     static private final String TAG = "SoundTriggerMiddlewareService";
 
-    final ISoundTriggerMiddlewareService mDelegate;
-    final Context mContext;
-    Set<Integer> mModuleHandles;
+    private final ISoundTriggerMiddlewareService mDelegate;
+    private final Context mContext;
+    private Set<Integer> mModuleHandles;
 
     /**
      * Constructor for internal use only. Could be exposed for testing purposes in the future.
@@ -280,7 +280,7 @@
         }
 
         /** Activity state. */
-        public Activity activityState = Activity.LOADED;
+        Activity activityState = Activity.LOADED;
 
         /**
          * A map of known parameter support. A missing key means we don't know yet whether the
@@ -294,7 +294,7 @@
          *
          * @param modelParam The parameter key.
          */
-        public void checkSupported(int modelParam) {
+        void checkSupported(int modelParam) {
             if (!parameterSupport.containsKey(modelParam)) {
                 throw new IllegalStateException("Parameter has not been checked for support.");
             }
@@ -311,7 +311,7 @@
          * @param modelParam The parameter key.
          * @param value      The value.
          */
-        public void checkSupported(int modelParam, int value) {
+        void checkSupported(int modelParam, int value) {
             if (!parameterSupport.containsKey(modelParam)) {
                 throw new IllegalStateException("Parameter has not been checked for support.");
             }
@@ -329,7 +329,7 @@
          * @param modelParam The parameter key.
          * @param range      The parameter value range, or null if not supported.
          */
-        public void updateParameterSupport(int modelParam, @Nullable ModelParameterRange range) {
+        void updateParameterSupport(int modelParam, @Nullable ModelParameterRange range) {
             parameterSupport.put(modelParam, range);
         }
     }
@@ -338,27 +338,26 @@
      * Entry-point to this module: exposes the module as a {@link SystemService}.
      */
     public static final class Lifecycle extends SystemService {
-        private SoundTriggerMiddlewareService mService;
-
         public Lifecycle(Context context) {
             super(context);
         }
 
         @Override
         public void onStart() {
-            ISoundTriggerHw[] services;
-            try {
-                services = new ISoundTriggerHw[]{ISoundTriggerHw.getService(true)};
-                Log.d(TAG, "Connected to default ISoundTriggerHw");
-            } catch (Exception e) {
-                Log.e(TAG, "Failed to connect to default ISoundTriggerHw", e);
-                services = new ISoundTriggerHw[0];
-            }
+            HalFactory[] factories = new HalFactory[]{() -> {
+                try {
+                    Log.d(TAG, "Connecting to default ISoundTriggerHw");
+                    return ISoundTriggerHw.getService(true);
+                } catch (RemoteException e) {
+                    throw e.rethrowAsRuntimeException();
+                }
+            }};
 
-            mService = new SoundTriggerMiddlewareService(
-                    new SoundTriggerMiddlewareImpl(services, new AudioSessionProviderImpl()),
-                    getContext());
-            publishBinderService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE, mService);
+            publishBinderService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE,
+                    new SoundTriggerMiddlewareService(
+                            new SoundTriggerMiddlewareImpl(factories,
+                                    new AudioSessionProviderImpl()),
+                            getContext()));
         }
     }
 
@@ -370,7 +369,7 @@
             DeathRecipient {
         private final ISoundTriggerCallback mCallback;
         private ISoundTriggerModule mDelegate;
-        private Map<Integer, ModelState> mLoadedModels = new HashMap<>();
+        private @NonNull Map<Integer, ModelState> mLoadedModels = new HashMap<>();
 
         ModuleService(@NonNull ISoundTriggerCallback callback) {
             mCallback = callback;
@@ -680,7 +679,7 @@
                 } catch (RemoteException e) {
                     // Dead client will be handled by binderDied() - no need to handle here.
                     // In any case, client callbacks are considered best effort.
-                    Log.e(TAG, "Client callback execption.", e);
+                    Log.e(TAG, "Client callback exception.", e);
                 }
             }
         }
@@ -696,20 +695,33 @@
                 } catch (RemoteException e) {
                     // Dead client will be handled by binderDied() - no need to handle here.
                     // In any case, client callbacks are considered best effort.
-                    Log.e(TAG, "Client callback execption.", e);
+                    Log.e(TAG, "Client callback exception.", e);
                 }
             }
         }
 
         @Override
-        public void onRecognitionAvailabilityChange(boolean available) throws RemoteException {
+        public void onRecognitionAvailabilityChange(boolean available) {
             synchronized (this) {
                 try {
                     mCallback.onRecognitionAvailabilityChange(available);
                 } catch (RemoteException e) {
                     // Dead client will be handled by binderDied() - no need to handle here.
                     // In any case, client callbacks are considered best effort.
-                    Log.e(TAG, "Client callback execption.", e);
+                    Log.e(TAG, "Client callback exception.", e);
+                }
+            }
+        }
+
+        @Override
+        public void onModuleDied() {
+            synchronized (this) {
+                try {
+                    mCallback.onModuleDied();
+                } catch (RemoteException e) {
+                    // Dead client will be handled by binderDied() - no need to handle here.
+                    // In any case, client callbacks are considered best effort.
+                    Log.e(TAG, "Client callback exception.", e);
                 }
             }
         }
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
index f024ede..adf16fa 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
@@ -30,7 +30,9 @@
 import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
 import android.media.soundtrigger_middleware.Status;
 import android.os.IBinder;
+import android.os.IHwBinder;
 import android.os.RemoteException;
+import android.os.ServiceSpecificException;
 import android.util.Log;
 
 import java.util.HashMap;
@@ -57,6 +59,7 @@
  * gracefully handle driver malfunction and such behavior will result in undefined behavior. If this
  * service is to used with an untrusted driver, the driver must be wrapped with validation / error
  * recovery code.
+ * <li>Recovery from driver death is supported.</li>
  * <li>RemoteExceptions thrown by the driver are treated as RuntimeExceptions - they are not
  * considered recoverable faults and should not occur in a properly functioning system.
  * <li>There is no binder instance associated with this implementation. Do not call asBinder().
@@ -79,27 +82,29 @@
  *
  * @hide
  */
-class SoundTriggerModule {
+class SoundTriggerModule implements IHwBinder.DeathRecipient {
     static private final String TAG = "SoundTriggerModule";
-    @NonNull private final ISoundTriggerHw2 mHalService;
+    @NonNull private HalFactory mHalFactory;
+    @NonNull private ISoundTriggerHw2 mHalService;
     @NonNull private final SoundTriggerMiddlewareImpl.AudioSessionProvider mAudioSessionProvider;
     private final Set<Session> mActiveSessions = new HashSet<>();
     private int mNumLoadedModels = 0;
-    private SoundTriggerModuleProperties mProperties = null;
+    private SoundTriggerModuleProperties mProperties;
     private boolean mRecognitionAvailable;
 
     /**
      * Ctor.
      *
-     * @param halService The underlying HAL driver.
+     * @param halFactory A factory for the underlying HAL driver.
      */
-    SoundTriggerModule(@NonNull android.hardware.soundtrigger.V2_0.ISoundTriggerHw halService,
+    SoundTriggerModule(@NonNull HalFactory halFactory,
             @NonNull SoundTriggerMiddlewareImpl.AudioSessionProvider audioSessionProvider) {
-        assert halService != null;
-        mHalService = new SoundTriggerHw2Compat(halService);
+        assert halFactory != null;
+        mHalFactory = halFactory;
         mAudioSessionProvider = audioSessionProvider;
-        mProperties = ConversionUtil.hidl2aidlProperties(mHalService.getProperties());
 
+        attachToHal();
+        mProperties = ConversionUtil.hidl2aidlProperties(mHalService.getProperties());
         // We conservatively assume that external capture is active until explicitly told otherwise.
         mRecognitionAvailable = mProperties.concurrentCapture;
     }
@@ -117,7 +122,7 @@
      * @return The interface through which this module can be controlled.
      */
     synchronized @NonNull
-    Session attach(@NonNull ISoundTriggerCallback callback) {
+    ISoundTriggerModule attach(@NonNull ISoundTriggerCallback callback) {
         Log.d(TAG, "attach()");
         Session session = new Session(callback);
         mActiveSessions.add(session);
@@ -145,6 +150,7 @@
      */
     synchronized void setExternalCaptureState(boolean active) {
         Log.d(TAG, String.format("setExternalCaptureState(active=%b)", active));
+
         if (mProperties.concurrentCapture) {
             // If we support concurrent capture, we don't care about any of this.
             return;
@@ -162,6 +168,23 @@
         }
     }
 
+    @Override
+    public synchronized  void serviceDied(long cookie) {
+        Log.w(TAG, String.format("Underlying HAL driver died."));
+        for (Session session : mActiveSessions) {
+            session.moduleDied();
+        }
+        attachToHal();
+    }
+
+    /**
+     * Attached to the HAL service via factory.
+     */
+    private void attachToHal() {
+        mHalService = new SoundTriggerHw2Compat(mHalFactory.create());
+        mHalService.linkToDeath(this, 0);
+    }
+
     /**
      * Remove session from the list of active sessions.
      *
@@ -204,7 +227,11 @@
         public void detach() {
             Log.d(TAG, "detach()");
             synchronized (SoundTriggerModule.this) {
+                if (mCallback == null) {
+                    return;
+                }
                 removeSession(this);
+                mCallback = null;
             }
         }
 
@@ -212,6 +239,7 @@
         public int loadModel(@NonNull SoundModel model) {
             Log.d(TAG, String.format("loadModel(model=%s)", model));
             synchronized (SoundTriggerModule.this) {
+                checkValid();
                 if (mNumLoadedModels == mProperties.maxSoundModels) {
                     throw new RecoverableException(Status.RESOURCE_CONTENTION,
                             "Maximum number of models loaded.");
@@ -227,6 +255,7 @@
         public int loadPhraseModel(@NonNull PhraseSoundModel model) {
             Log.d(TAG, String.format("loadPhraseModel(model=%s)", model));
             synchronized (SoundTriggerModule.this) {
+                checkValid();
                 if (mNumLoadedModels == mProperties.maxSoundModels) {
                     throw new RecoverableException(Status.RESOURCE_CONTENTION,
                             "Maximum number of models loaded.");
@@ -243,6 +272,7 @@
         public void unloadModel(int modelHandle) {
             Log.d(TAG, String.format("unloadModel(handle=%d)", modelHandle));
             synchronized (SoundTriggerModule.this) {
+                checkValid();
                 mLoadedModels.get(modelHandle).unload();
                 --mNumLoadedModels;
             }
@@ -253,6 +283,7 @@
             Log.d(TAG,
                     String.format("startRecognition(handle=%d, config=%s)", modelHandle, config));
             synchronized (SoundTriggerModule.this) {
+                checkValid();
                 mLoadedModels.get(modelHandle).startRecognition(config);
             }
         }
@@ -269,26 +300,28 @@
         public void forceRecognitionEvent(int modelHandle) {
             Log.d(TAG, String.format("forceRecognitionEvent(handle=%d)", modelHandle));
             synchronized (SoundTriggerModule.this) {
+                checkValid();
                 mLoadedModels.get(modelHandle).forceRecognitionEvent();
             }
         }
 
         @Override
-        public void setModelParameter(int modelHandle, int modelParam, int value)
-                throws RemoteException {
+        public void setModelParameter(int modelHandle, int modelParam, int value) {
             Log.d(TAG,
                     String.format("setModelParameter(handle=%d, param=%d, value=%d)", modelHandle,
                             modelParam, value));
             synchronized (SoundTriggerModule.this) {
+                checkValid();
                 mLoadedModels.get(modelHandle).setParameter(modelParam, value);
             }
         }
 
         @Override
-        public int getModelParameter(int modelHandle, int modelParam) throws RemoteException {
+        public int getModelParameter(int modelHandle, int modelParam) {
             Log.d(TAG, String.format("getModelParameter(handle=%d, param=%d)", modelHandle,
                     modelParam));
             synchronized (SoundTriggerModule.this) {
+                checkValid();
                 return mLoadedModels.get(modelHandle).getParameter(modelParam);
             }
         }
@@ -299,6 +332,7 @@
             Log.d(TAG, String.format("queryModelParameterSupport(handle=%d, param=%d)", modelHandle,
                     modelParam));
             synchronized (SoundTriggerModule.this) {
+                checkValid();
                 return mLoadedModels.get(modelHandle).queryModelParameterSupport(modelParam);
             }
         }
@@ -322,6 +356,25 @@
             }
         }
 
+        /**
+         * The underlying module HAL is dead.
+         */
+        private void moduleDied() {
+            try {
+                mCallback.onModuleDied();
+                removeSession(this);
+                mCallback = null;
+            } catch (RemoteException e) {
+                e.rethrowAsRuntimeException();
+            }
+        }
+
+        private void checkValid() {
+            if (mCallback == null) {
+                throw new ServiceSpecificException(Status.DEAD_OBJECT);
+            }
+        }
+
         @Override
         public @NonNull
         IBinder asBinder() {
@@ -350,10 +403,6 @@
                 SoundTriggerModule.this.notifyAll();
             }
 
-            private void waitStateChange() throws InterruptedException {
-                SoundTriggerModule.this.wait();
-            }
-
             private int load(@NonNull SoundModel model) {
                 mModelType = model.type;
                 ISoundTriggerHw.SoundModel hidlModel = ConversionUtil.aidl2hidlSoundModel(model);
diff --git a/services/core/java/com/android/server/stats/StatsPullAtomService.java b/services/core/java/com/android/server/stats/StatsPullAtomService.java
index 5ee7eff..1f7ed8a 100644
--- a/services/core/java/com/android/server/stats/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/StatsPullAtomService.java
@@ -210,6 +210,18 @@
     @Override
     public void onStart() {
         mStatsManager = (StatsManager) mContext.getSystemService(Context.STATS_MANAGER);
+
+        // Used to initialize the CPU Frequency atom.
+        PowerProfile powerProfile = new PowerProfile(mContext);
+        final int numClusters = powerProfile.getNumCpuClusters();
+        mKernelCpuSpeedReaders = new KernelCpuSpeedReader[numClusters];
+        int firstCpuOfCluster = 0;
+        for (int i = 0; i < numClusters; i++) {
+            final int numSpeedSteps = powerProfile.getNumSpeedStepsInCpuCluster(i);
+            mKernelCpuSpeedReaders[i] = new KernelCpuSpeedReader(firstCpuOfCluster,
+                    numSpeedSteps);
+            firstCpuOfCluster += powerProfile.getNumCoresInCpuCluster(i);
+        }
     }
 
     @Override
@@ -610,52 +622,193 @@
         return StatsManager.PULL_SUCCESS;
     }
 
+    private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader();
+    private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
+
     private void registerKernelWakelock() {
-        // No op.
+        int tagId = StatsLog.KERNEL_WAKELOCK;
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                /* PullAtomMetadata */ null,
+                (atomTag, data) -> pullKernelWakelock(atomTag, data),
+                Executors.newSingleThreadExecutor()
+        );
     }
 
-    private void pullKernelWakelock() {
-        // No op.
+    private int pullKernelWakelock(int atomTag, List<StatsEvent> pulledData) {
+        final KernelWakelockStats wakelockStats =
+                mKernelWakelockReader.readKernelWakelockStats(mTmpWakelockStats);
+        for (Map.Entry<String, KernelWakelockStats.Entry> ent : wakelockStats.entrySet()) {
+            String name = ent.getKey();
+            KernelWakelockStats.Entry kws = ent.getValue();
+            StatsEvent e = StatsEvent.newBuilder()
+                    .setAtomId(atomTag)
+                    .writeString(name)
+                    .writeInt(kws.mCount)
+                    .writeInt(kws.mVersion)
+                    .writeLong(kws.mTotalTime)
+                    .build();
+            pulledData.add(e);
+        }
+        return StatsManager.PULL_SUCCESS;
     }
 
+    private KernelCpuSpeedReader[] mKernelCpuSpeedReaders;
+    // Disables throttler on CPU time readers.
+    private KernelCpuUidUserSysTimeReader mCpuUidUserSysTimeReader =
+            new KernelCpuUidUserSysTimeReader(false);
+    private KernelCpuUidFreqTimeReader mCpuUidFreqTimeReader =
+            new KernelCpuUidFreqTimeReader(false);
+    private KernelCpuUidActiveTimeReader mCpuUidActiveTimeReader =
+            new KernelCpuUidActiveTimeReader(false);
+    private KernelCpuUidClusterTimeReader mCpuUidClusterTimeReader =
+            new KernelCpuUidClusterTimeReader(false);
+
     private void registerCpuTimePerFreq() {
-        // No op.
+        int tagId = StatsLog.CPU_TIME_PER_FREQ;
+        PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+                .setAdditiveFields(new int[] {3})
+                .build();
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                metadata,
+                (atomTag, data) -> pullCpuTimePerFreq(atomTag, data),
+                Executors.newSingleThreadExecutor()
+        );
     }
 
-    private void pullCpuTimePerFreq() {
-        // No op.
+    private int pullCpuTimePerFreq(int atomTag, List<StatsEvent> pulledData) {
+        for (int cluster = 0; cluster < mKernelCpuSpeedReaders.length; cluster++) {
+            long[] clusterTimeMs = mKernelCpuSpeedReaders[cluster].readAbsolute();
+            if (clusterTimeMs != null) {
+                for (int speed = clusterTimeMs.length - 1; speed >= 0; --speed) {
+                    StatsEvent e = StatsEvent.newBuilder()
+                            .setAtomId(atomTag)
+                            .writeInt(cluster)
+                            .writeInt(speed)
+                            .writeLong(clusterTimeMs[speed])
+                            .build();
+                    pulledData.add(e);
+                }
+            }
+        }
+        return StatsManager.PULL_SUCCESS;
     }
 
     private void registerCpuTimePerUid() {
-        // No op.
+        int tagId = StatsLog.CPU_TIME_PER_UID;
+        PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+                .setAdditiveFields(new int[] {2, 3})
+                .build();
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                metadata,
+                (atomTag, data) -> pullCpuTimePerUid(atomTag, data),
+                Executors.newSingleThreadExecutor()
+        );
     }
 
-    private void pullCpuTimePerUid() {
-        // No op.
+    private int pullCpuTimePerUid(int atomTag, List<StatsEvent> pulledData) {
+        mCpuUidUserSysTimeReader.readAbsolute((uid, timesUs) -> {
+            long userTimeUs = timesUs[0], systemTimeUs = timesUs[1];
+            StatsEvent e = StatsEvent.newBuilder()
+                    .setAtomId(atomTag)
+                    .writeInt(uid)
+                    .writeLong(userTimeUs)
+                    .writeLong(systemTimeUs)
+                    .build();
+            pulledData.add(e);
+        });
+        return StatsManager.PULL_SUCCESS;
     }
 
     private void registerCpuTimePerUidFreq() {
-        // No op.
+        // the throttling is 3sec, handled in
+        // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
+        int tagId = StatsLog.CPU_TIME_PER_UID_FREQ;
+        PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+                .setAdditiveFields(new int[] {4})
+                .build();
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                metadata,
+                (atomTag, data) -> pullCpuTimeperUidFreq(atomTag, data),
+                Executors.newSingleThreadExecutor()
+        );
     }
 
-    private void pullCpuTimeperUidFreq() {
-        // No op.
+    private int pullCpuTimeperUidFreq(int atomTag, List<StatsEvent> pulledData) {
+        mCpuUidFreqTimeReader.readAbsolute((uid, cpuFreqTimeMs) -> {
+            for (int freqIndex = 0; freqIndex < cpuFreqTimeMs.length; ++freqIndex) {
+                if (cpuFreqTimeMs[freqIndex] != 0) {
+                    StatsEvent e = StatsEvent.newBuilder()
+                            .setAtomId(atomTag)
+                            .writeInt(uid)
+                            .writeInt(freqIndex)
+                            .writeLong(cpuFreqTimeMs[freqIndex])
+                            .build();
+                    pulledData.add(e);
+                }
+            }
+        });
+        return StatsManager.PULL_SUCCESS;
     }
 
     private void registerCpuActiveTime() {
-        // No op.
+        // the throttling is 3sec, handled in
+        // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
+        int tagId = StatsLog.CPU_ACTIVE_TIME;
+        PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+                .setAdditiveFields(new int[] {2})
+                .build();
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                metadata,
+                (atomTag, data) -> pullCpuActiveTime(atomTag, data),
+                Executors.newSingleThreadExecutor()
+        );
     }
 
-    private void pullCpuActiveTime() {
-        // No op.
+    private int pullCpuActiveTime(int atomTag, List<StatsEvent> pulledData) {
+        mCpuUidActiveTimeReader.readAbsolute((uid, cpuActiveTimesMs) -> {
+            StatsEvent e = StatsEvent.newBuilder()
+                    .setAtomId(atomTag)
+                    .writeInt(uid)
+                    .writeLong(cpuActiveTimesMs)
+                    .build();
+            pulledData.add(e);
+        });
+        return StatsManager.PULL_SUCCESS;
     }
 
     private void registerCpuClusterTime() {
-        // No op.
+        // the throttling is 3sec, handled in
+        // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
+        int tagId = StatsLog.CPU_CLUSTER_TIME;
+        PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+                .setAdditiveFields(new int[] {3})
+                .build();
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                metadata,
+                (atomTag, data) -> pullCpuClusterTime(atomTag, data),
+                Executors.newSingleThreadExecutor()
+        );
     }
 
-    private int pullCpuClusterTime() {
-        return 0;
+    private int pullCpuClusterTime(int atomTag, List<StatsEvent> pulledData) {
+        mCpuUidClusterTimeReader.readAbsolute((uid, cpuClusterTimesMs) -> {
+            for (int i = 0; i < cpuClusterTimesMs.length; i++) {
+                StatsEvent e = StatsEvent.newBuilder()
+                        .setAtomId(atomTag)
+                        .writeInt(uid)
+                        .writeInt(i)
+                        .writeLong(cpuClusterTimesMs[i])
+                        .build();
+                pulledData.add(e);
+            }
+        });
+        return StatsManager.PULL_SUCCESS;
     }
 
     private void registerWifiActivityInfo() {
@@ -711,11 +864,22 @@
     }
 
     private void registerSystemUptime() {
-        // No op.
+        int tagId = StatsLog.SYSTEM_UPTIME;
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                null, // use default PullAtomMetadata values
+                (atomTag, data) -> pullSystemUptime(atomTag, data),
+                Executors.newSingleThreadExecutor()
+        );
     }
 
-    private void pullSystemUptime() {
-        // No op.
+    private int pullSystemUptime(int atomTag, List<StatsEvent> pulledData) {
+        StatsEvent e = StatsEvent.newBuilder()
+                .setAtomId(atomTag)
+                .writeLong(SystemClock.elapsedRealtime())
+                .build();
+        pulledData.add(e);
+        return StatsManager.PULL_SUCCESS;
     }
 
     private void registerRemainingBatteryCapacity() {
@@ -990,11 +1154,30 @@
     }
 
     private void registerBuildInformation() {
-        // No op.
+        int tagId = StatsLog.BUILD_INFORMATION;
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                null, // use default PullAtomMetadata values
+                (atomTag, data) -> pullBuildInformation(atomTag, data),
+                BackgroundThread.getExecutor()
+        );
     }
 
-    private void pullBuildInformation() {
-        // No op.
+    private int pullBuildInformation(int atomTag, List<StatsEvent> pulledData) {
+        StatsEvent e = StatsEvent.newBuilder()
+                .setAtomId(atomTag)
+                .writeString(Build.FINGERPRINT)
+                .writeString(Build.BRAND)
+                .writeString(Build.PRODUCT)
+                .writeString(Build.DEVICE)
+                .writeString(Build.VERSION.RELEASE)
+                .writeString(Build.ID)
+                .writeString(Build.VERSION.INCREMENTAL)
+                .writeString(Build.TYPE)
+                .writeString(Build.TAGS)
+                .build();
+        pulledData.add(e);
+        return StatsManager.PULL_SUCCESS;
     }
 
     private void registerRoleHolder() {
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
index c964795..468b806 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
@@ -61,10 +61,10 @@
         /** Acquire a suitable wake lock. Must be followed by {@link #releaseWakeLock()} */
         void acquireWakeLock();
 
-        /** Returns the elapsedRealtimeMillis clock value. The WakeLock must be held. */
+        /** Returns the elapsedRealtimeMillis clock value. */
         long elapsedRealtimeMillis();
 
-        /** Returns the system clock value. The WakeLock must be held. */
+        /** Returns the system clock value. */
         long systemClockMillis();
 
         /** Sets the device system clock. The WakeLock must be held. */
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java
index 9b89d94..19484db 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java
@@ -88,13 +88,11 @@
 
     @Override
     public long elapsedRealtimeMillis() {
-        checkWakeLockHeld();
         return SystemClock.elapsedRealtime();
     }
 
     @Override
     public long systemClockMillis() {
-        checkWakeLockHeld();
         return System.currentTimeMillis();
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 320be2d..19bc560 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1633,12 +1633,7 @@
         requestedVrComponent = (aInfo.requestedVrComponent == null) ?
                 null : ComponentName.unflattenFromString(aInfo.requestedVrComponent);
 
-        lockTaskLaunchMode = aInfo.lockTaskLaunchMode;
-        if (info.applicationInfo.isPrivilegedApp()
-                && (lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_ALWAYS
-                || lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_NEVER)) {
-            lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_DEFAULT;
-        }
+        lockTaskLaunchMode = getLockTaskLaunchMode(aInfo, options);
 
         if (options != null) {
             pendingOptions = options;
@@ -1646,15 +1641,27 @@
             if (usageReport != null) {
                 appTimeTracker = new AppTimeTracker(usageReport);
             }
-            final boolean useLockTask = pendingOptions.getLockTaskMode();
-            if (useLockTask && lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_DEFAULT) {
-                lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED;
-            }
             // Gets launch display id from options. It returns INVALID_DISPLAY if not set.
             mHandoverLaunchDisplayId = options.getLaunchDisplayId();
         }
     }
 
+    static int getLockTaskLaunchMode(ActivityInfo aInfo, @Nullable ActivityOptions options) {
+        int lockTaskLaunchMode = aInfo.lockTaskLaunchMode;
+        if (aInfo.applicationInfo.isPrivilegedApp()
+                && (lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_ALWAYS
+                || lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_NEVER)) {
+            lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_DEFAULT;
+        }
+        if (options != null) {
+            final boolean useLockTask = options.getLockTaskMode();
+            if (useLockTask && lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_DEFAULT) {
+                lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED;
+            }
+        }
+        return lockTaskLaunchMode;
+    }
+
     @Override
     ActivityRecord asActivityRecord() {
         // I am an activity record!
@@ -2130,8 +2137,10 @@
                 (intent == null || (intent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0);
     }
 
+    @Override
     boolean isFocusable() {
-        return mRootWindowContainer.isFocusable(this, isAlwaysFocusable());
+        return super.isFocusable()
+                && (getWindowConfiguration().canReceiveKeys() || isAlwaysFocusable());
     }
 
     boolean isResizeable() {
@@ -2482,7 +2491,7 @@
             // We are finishing the top focused activity and its stack has nothing to be focused so
             // the next focusable stack should be focused.
             if (mayAdjustTop
-                    && (stack.topRunningActivity() == null || !stack.isFocusable())) {
+                    && (stack.topRunningActivity() == null || !stack.isTopActivityFocusable())) {
                 if (shouldAdjustGlobalFocus) {
                     // Move the entire hierarchy to top with updating global top resumed activity
                     // and focused application if needed.
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 9fd3ea4..f5fba8e 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -161,9 +161,9 @@
 import android.view.Display;
 import android.view.DisplayCutout;
 import android.view.DisplayInfo;
+import android.view.ITaskOrganizer;
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
-import android.view.ITaskOrganizer;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -1248,13 +1248,20 @@
         }
     }
 
+    @Override
     boolean isFocusable() {
+        return super.isFocusable() && !(inSplitScreenPrimaryWindowingMode()
+                && mRootWindowContainer.mIsDockMinimized);
+    }
+
+    boolean isTopActivityFocusable() {
         final ActivityRecord r = topRunningActivity();
-        return mRootWindowContainer.isFocusable(this, r != null && r.isFocusable());
+        return r != null ? r.isFocusable()
+                : (isFocusable() && getWindowConfiguration().canReceiveKeys());
     }
 
     boolean isFocusableAndVisible() {
-        return isFocusable() && shouldBeVisible(null /* starting */);
+        return isTopActivityFocusable() && shouldBeVisible(null /* starting */);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index df97caa..d61d29d 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -52,6 +52,7 @@
 import android.os.UserManager;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.BlockedAppActivity;
 import com.android.internal.app.HarmfulAppWarningActivity;
 import com.android.internal.app.SuspendedAppActivity;
 import com.android.internal.app.UnlaunchableAppActivity;
@@ -166,6 +167,9 @@
             // no user action can undo this.
             return true;
         }
+        if (interceptLockTaskModeViolationPackageIfNeeded()) {
+            return true;
+        }
         if (interceptHarmfulAppIfNeeded()) {
             // If the app has a "harmful app" warning associated with it, we should ask to uninstall
             // before issuing the work challenge.
@@ -262,6 +266,25 @@
         return true;
     }
 
+    private boolean interceptLockTaskModeViolationPackageIfNeeded() {
+        if (mAInfo == null || mAInfo.applicationInfo == null) {
+            return false;
+        }
+        LockTaskController controller = mService.getLockTaskController();
+        String packageName = mAInfo.applicationInfo.packageName;
+        int lockTaskLaunchMode = ActivityRecord.getLockTaskLaunchMode(mAInfo, mActivityOptions);
+        if (controller.isActivityAllowed(mUserId, packageName, lockTaskLaunchMode)) {
+            return false;
+        }
+        mIntent = BlockedAppActivity.createIntent(mUserId, mAInfo.applicationInfo.packageName);
+        mCallingPid = mRealCallingPid;
+        mCallingUid = mRealCallingUid;
+        mResolvedType = null;
+        mRInfo = mSupervisor.resolveIntent(mIntent, mResolvedType, mUserId, 0, mRealCallingUid);
+        mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, null /*profilerInfo*/);
+        return true;
+    }
+
     private boolean interceptWorkProfileChallengeIfNeeded() {
         final Intent interceptingIntent = interceptWithConfirmCredentialsIfNeeded(mAInfo, mUserId);
         if (interceptingIntent == null) {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 61ba15c..6587226 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1569,7 +1569,7 @@
         if (mDoResume) {
             final ActivityRecord topTaskActivity =
                     mStartActivity.getTask().topRunningActivityLocked();
-            if (!mTargetStack.isFocusable()
+            if (!mTargetStack.isTopActivityFocusable()
                     || (topTaskActivity != null && topTaskActivity.isTaskOverlay()
                     && mStartActivity != topTaskActivity)) {
                 // If the activity is not focusable, we can't resume it, but still would like to
@@ -1588,7 +1588,7 @@
                 // will not update the focused stack.  If starting the new activity now allows the
                 // task stack to be focusable, then ensure that we now update the focused stack
                 // accordingly.
-                if (mTargetStack.isFocusable()
+                if (mTargetStack.isTopActivityFocusable()
                         && !mRootWindowContainer.isTopDisplayFocusedStack(mTargetStack)) {
                     mTargetStack.moveToFront("startActivityInner");
                 }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index ded603c..e308f6b 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -28,7 +28,7 @@
 import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
 import static android.Manifest.permission.STOP_APP_SWITCHES;
 import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
-import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
+import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.ActivityTaskManager.RESIZE_MODE_PRESERVE_WINDOW;
 import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
@@ -248,7 +248,6 @@
 import com.android.internal.policy.KeyguardDismissCallback;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastPrintWriter;
-import com.android.internal.util.Preconditions;
 import com.android.internal.util.function.pooled.PooledConsumer;
 import com.android.internal.util.function.pooled.PooledFunction;
 import com.android.internal.util.function.pooled.PooledLambda;
@@ -345,6 +344,10 @@
     /** This activity is being relaunched due to a free-resize operation. */
     public static final int RELAUNCH_REASON_FREE_RESIZE = 2;
 
+    /** Flag indicating that an applied transaction may have effected lifecycle */
+    private static final int TRANSACT_EFFECTS_CLIENT_CONFIG = 1;
+    private static final int TRANSACT_EFFECTS_LIFECYCLE = 1 << 1;
+
     Context mContext;
 
     /**
@@ -1436,7 +1439,7 @@
 
     int handleIncomingUser(int callingPid, int callingUid, int userId, String name) {
         return mAmInternal.handleIncomingUser(callingPid, callingUid, userId, false /* allowAll */,
-                ALLOW_FULL_ONLY, name, null /* callerPackage */);
+                ALLOW_NON_FULL, name, null /* callerPackage */);
     }
 
     @Override
@@ -3300,7 +3303,7 @@
         }
     }
 
-    private void sanitizeAndApplyConfigChange(ConfigurationContainer container,
+    private int sanitizeAndApplyChange(ConfigurationContainer container,
             WindowContainerTransaction.Change change) {
         if (!(container instanceof Task || container instanceof ActivityStack)) {
             throw new RuntimeException("Invalid token in task transaction");
@@ -3312,12 +3315,22 @@
         configMask &= ActivityInfo.CONFIG_WINDOW_CONFIGURATION
                 | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
         windowMask &= WindowConfiguration.WINDOW_CONFIG_BOUNDS;
-        Configuration c = new Configuration(container.getRequestedOverrideConfiguration());
-        c.setTo(change.getConfiguration(), configMask, windowMask);
-        container.onRequestedOverrideConfigurationChanged(c);
-        // TODO(b/145675353): remove the following once we could apply new bounds to the
-        // pinned stack together with its children.
-        resizePinnedStackIfNeeded(container, configMask, windowMask, c);
+        int effects = 0;
+        if (configMask != 0) {
+            Configuration c = new Configuration(container.getRequestedOverrideConfiguration());
+            c.setTo(change.getConfiguration(), configMask, windowMask);
+            container.onRequestedOverrideConfigurationChanged(c);
+            // TODO(b/145675353): remove the following once we could apply new bounds to the
+            // pinned stack together with its children.
+            resizePinnedStackIfNeeded(container, configMask, windowMask, c);
+            effects |= TRANSACT_EFFECTS_CLIENT_CONFIG;
+        }
+        if ((change.getChangeMask() & WindowContainerTransaction.Change.CHANGE_FOCUSABLE) != 0) {
+            if (container.setFocusable(change.getFocusable())) {
+                effects |= TRANSACT_EFFECTS_LIFECYCLE;
+            }
+        }
+        return effects;
     }
 
     private void resizePinnedStackIfNeeded(ConfigurationContainer container, int configMask,
@@ -3334,9 +3347,9 @@
         }
     }
 
-    private void applyWindowContainerChange(ConfigurationContainer cc,
+    private int applyWindowContainerChange(ConfigurationContainer cc,
             WindowContainerTransaction.Change c) {
-        sanitizeAndApplyConfigChange(cc, c);
+        int effects = sanitizeAndApplyChange(cc, c);
 
         Rect enterPipBounds = c.getEnterPipBounds();
         if (enterPipBounds != null) {
@@ -3344,25 +3357,58 @@
             mStackSupervisor.updatePictureInPictureMode(tr,
                     enterPipBounds, true);
         }
+        return effects;
     }
 
     @Override
     public void applyContainerTransaction(WindowContainerTransaction t) {
         mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "applyContainerTransaction()");
+        if (t == null) {
+            return;
+        }
         long ident = Binder.clearCallingIdentity();
         try {
-            if (t == null) {
-                return;
-            }
             synchronized (mGlobalLock) {
-                Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries =
-                        t.getChanges().entrySet().iterator();
-                while (entries.hasNext()) {
-                    final Map.Entry<IBinder, WindowContainerTransaction.Change> entry =
-                            entries.next();
-                    final ConfigurationContainer cc = ConfigurationContainer.RemoteToken.fromBinder(
-                            entry.getKey()).getContainer();
-                    applyWindowContainerChange(cc, entry.getValue());
+                int effects = 0;
+                deferWindowLayout();
+                try {
+                    ArraySet<WindowContainer> haveConfigChanges = new ArraySet<>();
+                    Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries =
+                            t.getChanges().entrySet().iterator();
+                    while (entries.hasNext()) {
+                        final Map.Entry<IBinder, WindowContainerTransaction.Change> entry =
+                                entries.next();
+                        final ConfigurationContainer cc =
+                                ConfigurationContainer.RemoteToken.fromBinder(
+                                        entry.getKey()).getContainer();
+                        int containerEffect = applyWindowContainerChange(cc, entry.getValue());
+                        effects |= containerEffect;
+                        // Lifecycle changes will trigger ensureConfig for everything.
+                        if ((effects & TRANSACT_EFFECTS_LIFECYCLE) == 0
+                                && (containerEffect & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) {
+                            if (cc instanceof WindowContainer) {
+                                haveConfigChanges.add((WindowContainer) cc);
+                            }
+                        }
+                    }
+                    if ((effects & TRANSACT_EFFECTS_LIFECYCLE) != 0) {
+                        // Already calls ensureActivityConfig
+                        mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
+                    } else if ((effects & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) {
+                        final PooledConsumer f = PooledLambda.obtainConsumer(
+                                ActivityRecord::ensureActivityConfiguration,
+                                PooledLambda.__(ActivityRecord.class), 0,
+                                false /* preserveWindow */);
+                        try {
+                            for (int i = haveConfigChanges.size() - 1; i >= 0; --i) {
+                                haveConfigChanges.valueAt(i).forAllActivities(f);
+                            }
+                        } finally {
+                            f.recycle();
+                        }
+                    }
+                } finally {
+                    continueWindowLayout();
                 }
             }
         } finally {
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index d0310f1..1e60ce8 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -624,6 +624,21 @@
         return mFullConfiguration.windowConfiguration.isAlwaysOnTop();
     }
 
+    /**
+     * Returns {@code true} if this container is focusable. Generally, if a parent is not focusable,
+     * this will not be focusable either.
+     */
+    boolean isFocusable() {
+        // TODO(split): Move this to WindowContainer once Split-screen is based on a WindowContainer
+        //              like DisplayArea vs. TaskTiles.
+        ConfigurationContainer parent = getParent();
+        return parent == null || parent.isFocusable();
+    }
+
+    boolean setFocusable(boolean focusable) {
+        return false;
+    }
+
     boolean hasChild() {
         return getChildCount() > 0;
     }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index ba9d757..908c4f1 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -5915,7 +5915,7 @@
             final ActivityRecord resumedActivity = stack.getResumedActivity();
             if (resumedActivity != null
                     && (stack.getVisibility(resuming) != STACK_VISIBILITY_VISIBLE
-                    || !stack.isFocusable())) {
+                    || !stack.isTopActivityFocusable())) {
                 if (DEBUG_STATES) Slog.d(TAG_STATES, "pauseBackStacks: stack=" + stack
                         + " mResumedActivity=" + resumedActivity);
                 someActivityPaused |= stack.startPausingLocked(userLeaving, false /* uiSleeping*/,
@@ -6238,7 +6238,7 @@
             for (int i = getStackCount() - 1; i >= 0; --i) {
                 final ActivityStack stack = getStackAt(i);
                 // Only consider focusable stacks other than the current focused one.
-                if (stack == focusedStack || !stack.isFocusable()) {
+                if (stack == focusedStack || !stack.isTopActivityFocusable()) {
                     continue;
                 }
                 topRunning = stack.topRunningActivity();
diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
index c09834f..9d985d7 100644
--- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
+++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
@@ -75,7 +75,8 @@
         // activities are actually behind other fullscreen activities, but still required
         // to be visible (such as performing Recents animation).
         final boolean resumeTopActivity = mTop != null && !mTop.mLaunchTaskBehind
-                && mContiner.isFocusable() && mContiner.isInStackLocked(starting) == null;
+                && mContiner.isTopActivityFocusable()
+                && mContiner.isInStackLocked(starting) == null;
 
         final PooledConsumer f = PooledLambda.obtainConsumer(
                 EnsureActivitiesVisibleHelper::setActivityVisibilityState, this,
diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java
index 02413bb..33b0453 100644
--- a/services/core/java/com/android/server/wm/LockTaskController.java
+++ b/services/core/java/com/android/server/wm/LockTaskController.java
@@ -23,6 +23,8 @@
 import static android.content.Context.DEVICE_POLICY_SERVICE;
 import static android.content.Context.STATUS_BAR_SERVICE;
 import static android.content.Intent.ACTION_CALL_EMERGENCY;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
 import static android.os.UserHandle.USER_ALL;
 import static android.os.UserHandle.USER_CURRENT;
 import static android.telecom.TelecomManager.EMERGENCY_DIALER_COMPONENT;
@@ -339,6 +341,20 @@
                 & DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD) != 0;
     }
 
+    boolean isActivityAllowed(int userId, String packageName, int lockTaskLaunchMode) {
+        if (mLockTaskModeState != LOCK_TASK_MODE_LOCKED) {
+            return true;
+        }
+        switch (lockTaskLaunchMode) {
+            case LOCK_TASK_LAUNCH_MODE_ALWAYS:
+                return true;
+            case LOCK_TASK_LAUNCH_MODE_NEVER:
+                return false;
+            default:
+        }
+        return isPackageWhitelisted(userId, packageName);
+    }
+
     private boolean isEmergencyCallTask(Task task) {
         final Intent intent = task.intent;
         if (intent == null) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index c3e815d..2f726e9 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1860,14 +1860,6 @@
         return null;
     }
 
-    boolean isFocusable(ConfigurationContainer container, boolean alwaysFocusable) {
-        if (container.inSplitScreenPrimaryWindowingMode() && mIsDockMinimized) {
-            return false;
-        }
-
-        return container.getWindowConfiguration().canReceiveKeys() || alwaysFocusable;
-    }
-
     boolean isTopDisplayFocusedStack(ActivityStack stack) {
         return stack != null && stack == getTopDisplayFocusedStack();
     }
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index d36a5d4..38a7000 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -53,7 +53,7 @@
     private static final String SNAPSHOTS_DIRNAME = "snapshots";
     private static final String REDUCED_POSTFIX = "_reduced";
     private static final float REDUCED_SCALE = .5f;
-    private static final float LOW_RAM_REDUCED_SCALE = .6f;
+    private static final float LOW_RAM_REDUCED_SCALE = .8f;
     static final boolean DISABLE_FULL_SIZED_BITMAPS = ActivityManager.isLowRamDeviceStatic();
     private static final long DELAY_MS = 100;
     private static final int QUALITY = 95;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index c38868a..3b2d519 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -247,6 +247,8 @@
 
     private MagnificationSpec mLastMagnificationSpec;
 
+    private boolean mIsFocusable = true;
+
     WindowContainer(WindowManagerService wms) {
         mWmService = wms;
         mPendingTransaction = wms.mTransactionFactory.get();
@@ -851,6 +853,21 @@
         return false;
     }
 
+    @Override
+    boolean isFocusable() {
+        return super.isFocusable() && mIsFocusable;
+    }
+
+    /** Set whether this container or its children can be focusable */
+    @Override
+    boolean setFocusable(boolean focusable) {
+        if (mIsFocusable == focusable) {
+            return false;
+        }
+        mIsFocusable = focusable;
+        return true;
+    }
+
     /**
      * @return Whether this child is on top of the window hierarchy.
      */
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 74fdba1..e3b593e9 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -4602,13 +4602,13 @@
 
                     if (newFocus != null) {
                         ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "Gaining focus: %s", newFocus);
-                        newFocus.reportFocusChangedSerialized(true, mInTouchMode);
+                        newFocus.reportFocusChangedSerialized(true);
                         notifyFocusChanged();
                     }
 
                     if (lastFocus != null) {
                         ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "Losing focus: %s", lastFocus);
-                        lastFocus.reportFocusChangedSerialized(false, mInTouchMode);
+                        lastFocus.reportFocusChangedSerialized(false);
                     }
                     break;
                 }
@@ -4626,7 +4626,7 @@
                     for (int i = 0; i < N; i++) {
                         ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "Losing delayed focus: %s",
                                 losers.get(i));
-                        losers.get(i).reportFocusChangedSerialized(false, mInTouchMode);
+                        losers.get(i).reportFocusChangedSerialized(false);
                     }
                     break;
                 }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index ba40f62..c2eb0e4 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3339,11 +3339,7 @@
      * Report a focus change.  Must be called with no locks held, and consistently
      * from the same serialized thread (such as dispatched from a handler).
      */
-    void reportFocusChangedSerialized(boolean focused, boolean inTouchMode) {
-        try {
-            mClient.windowFocusChanged(focused, inTouchMode);
-        } catch (RemoteException e) {
-        }
+    void reportFocusChangedSerialized(boolean focused) {
         if (mFocusCallbacks != null) {
             final int N = mFocusCallbacks.beginBroadcast();
             for (int i=0; i<N; i++) {
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 03969b0..77d814e 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -51,7 +51,7 @@
         "com_android_server_VibratorService.cpp",
         "com_android_server_PersistentDataBlockService.cpp",
         "com_android_server_GraphicsStatsService.cpp",
-        "com_android_server_am_AppCompactor.cpp",
+        "com_android_server_am_CachedAppOptimizer.cpp",
         "com_android_server_am_LowMemDetector.cpp",
         "com_android_server_incremental_IncrementalManagerService.cpp",
         "onload.cpp",
diff --git a/services/core/jni/com_android_server_am_AppCompactor.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
similarity index 89%
rename from services/core/jni/com_android_server_am_AppCompactor.cpp
rename to services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index de6aa8b..6a6da0e 100644
--- a/services/core/jni/com_android_server_am_AppCompactor.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "AppCompactor"
+#define LOG_TAG "CachedAppOptimizer"
 //#define LOG_NDEBUG 0
 
 #include <dirent.h>
@@ -42,7 +42,7 @@
 // or potentially some mainline modules. The only process that should definitely
 // not be compacted is system_server, since compacting system_server around the
 // time of BOOT_COMPLETE could result in perceptible issues.
-static void com_android_server_am_AppCompactor_compactSystem(JNIEnv *, jobject) {
+static void com_android_server_am_CachedAppOptimizer_compactSystem(JNIEnv *, jobject) {
     std::unique_ptr<DIR, decltype(&closedir)> proc(opendir("/proc"), closedir);
     struct dirent* current;
     while ((current = readdir(proc.get()))) {
@@ -76,12 +76,12 @@
 
 static const JNINativeMethod sMethods[] = {
     /* name, signature, funcPtr */
-    {"compactSystem", "()V", (void*)com_android_server_am_AppCompactor_compactSystem},
+    {"compactSystem", "()V", (void*)com_android_server_am_CachedAppOptimizer_compactSystem},
 };
 
-int register_android_server_am_AppCompactor(JNIEnv* env)
+int register_android_server_am_CachedAppOptimizer(JNIEnv* env)
 {
-    return jniRegisterNativeMethods(env, "com/android/server/am/AppCompactor",
+    return jniRegisterNativeMethods(env, "com/android/server/am/CachedAppOptimizer",
                                     sMethods, NELEM(sMethods));
 }
 
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index c0a6e4e..19fa062 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -54,7 +54,7 @@
 int register_android_server_net_NetworkStatsFactory(JNIEnv* env);
 int register_android_server_net_NetworkStatsService(JNIEnv* env);
 int register_android_server_security_VerityUtils(JNIEnv* env);
-int register_android_server_am_AppCompactor(JNIEnv* env);
+int register_android_server_am_CachedAppOptimizer(JNIEnv* env);
 int register_android_server_am_LowMemDetector(JNIEnv* env);
 int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
         JNIEnv* env);
@@ -106,7 +106,7 @@
     register_android_server_net_NetworkStatsFactory(env);
     register_android_server_net_NetworkStatsService(env);
     register_android_server_security_VerityUtils(env);
-    register_android_server_am_AppCompactor(env);
+    register_android_server_am_CachedAppOptimizer(env);
     register_android_server_am_LowMemDetector(env);
     register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
             env);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index b6a8ca4..0bd7392 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -71,11 +71,11 @@
 import android.util.DisplayMetrics;
 import android.util.EventLog;
 import android.util.Slog;
+import android.util.StatsLog;
 import android.view.WindowManager;
 import android.view.contentcapture.ContentCaptureManager;
 
 import com.android.internal.R;
-import com.android.internal.logging.MetricsLogger;
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.os.BinderInternal;
 import com.android.internal.util.ConcurrentUtils;
@@ -213,6 +213,8 @@
             "com.android.server.print.PrintManagerService";
     private static final String COMPANION_DEVICE_MANAGER_SERVICE_CLASS =
             "com.android.server.companion.CompanionDeviceManagerService";
+    private static final String STATS_COMPANION_APEX_PATH =
+            "/apex/com.android.os.statsd/javalib/service-statsd.jar";
     private static final String STATS_COMPANION_LIFECYCLE_CLASS =
             "com.android.server.stats.StatsCompanion$Lifecycle";
     private static final String STATS_PULL_ATOM_SERVICE_CLASS =
@@ -441,10 +443,12 @@
 
             // Here we go!
             Slog.i(TAG, "Entered the Android system server!");
-            int uptimeMillis = (int) SystemClock.elapsedRealtime();
+            final long uptimeMillis = SystemClock.elapsedRealtime();
             EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_SYSTEM_RUN, uptimeMillis);
             if (!mRuntimeRestart) {
-                MetricsLogger.histogram(null, "boot_system_server_init", uptimeMillis);
+                StatsLog.write(StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED,
+                        StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__SYSTEM_SERVER_INIT_START,
+                        uptimeMillis);
             }
 
             // In case the runtime switched since last boot (such as when
@@ -553,10 +557,12 @@
         StrictMode.initVmDefaults(null);
 
         if (!mRuntimeRestart && !isFirstBootOrUpgrade()) {
-            int uptimeMillis = (int) SystemClock.elapsedRealtime();
-            MetricsLogger.histogram(null, "boot_system_server_ready", uptimeMillis);
-            final int MAX_UPTIME_MILLIS = 60 * 1000;
-            if (uptimeMillis > MAX_UPTIME_MILLIS) {
+            final long uptimeMillis = SystemClock.elapsedRealtime();
+            StatsLog.write(StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED,
+                    StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__SYSTEM_SERVER_READY,
+                    uptimeMillis);
+            final long maxUptimeMillis = 60 * 1000;
+            if (uptimeMillis > maxUptimeMillis) {
                 Slog.wtf(SYSTEM_SERVER_TIMING_TAG,
                         "SystemServer init took too long. uptimeMillis=" + uptimeMillis);
             }
@@ -789,8 +795,9 @@
 
         // Start the package manager.
         if (!mRuntimeRestart) {
-            MetricsLogger.histogram(null, "boot_package_manager_init_start",
-                    (int) SystemClock.elapsedRealtime());
+            StatsLog.write(StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED,
+                    StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__PACKAGE_MANAGER_INIT_START,
+                    SystemClock.elapsedRealtime());
         }
 
         t.traceBegin("StartPackageManagerService");
@@ -806,8 +813,9 @@
         mPackageManager = mSystemContext.getPackageManager();
         t.traceEnd();
         if (!mRuntimeRestart && !isFirstBootOrUpgrade()) {
-            MetricsLogger.histogram(null, "boot_package_manager_init_ready",
-                    (int) SystemClock.elapsedRealtime());
+            StatsLog.write(StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED,
+                    StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__PACKAGE_MANAGER_INIT_READY,
+                    SystemClock.elapsedRealtime());
         }
         // Manages A/B OTA dexopting. This is a bootstrap service as we need it to rename
         // A/B artifacts after boot, before anything else might touch/need them.
@@ -1980,7 +1988,8 @@
 
         // Statsd helper
         t.traceBegin("StartStatsCompanion");
-        mSystemServiceManager.startService(STATS_COMPANION_LIFECYCLE_CLASS);
+        mSystemServiceManager.startServiceFromJar(
+                STATS_COMPANION_LIFECYCLE_CLASS, STATS_COMPANION_APEX_PATH);
         t.traceEnd();
 
         // Statsd pulled atoms
diff --git a/services/net/Android.bp b/services/net/Android.bp
index 9c7cfc1..cf84bdf 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -23,7 +23,6 @@
     name: "services-tethering-shared-srcs",
     srcs: [
         ":framework-annotations",
-        "java/android/net/util/NetdService.java",
         "java/android/net/util/NetworkConstants.java",
     ],
     visibility: ["//frameworks/base/packages/Tethering"],
diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java
index ef3da60..9569c6e 100644
--- a/services/people/java/com/android/server/people/PeopleService.java
+++ b/services/people/java/com/android/server/people/PeopleService.java
@@ -137,5 +137,14 @@
                 Slog.e(TAG, "Failed to calling callback" + e);
             }
         }
+
+        @Override
+        public byte[] backupConversationInfos(int userId) {
+            return new byte[0];
+        }
+
+        @Override
+        public void restoreConversationInfos(int userId, String key, byte[] payload) {
+        }
     }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AppCompactorTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AppCompactorTest.java
deleted file mode 100644
index 48e459f..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/am/AppCompactorTest.java
+++ /dev/null
@@ -1,680 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.ActivityManagerService.Injector;
-import static com.android.server.am.AppCompactor.compactActionIntToString;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.Context;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Process;
-import android.platform.test.annotations.Presubmit;
-import android.provider.DeviceConfig;
-import android.text.TextUtils;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.server.ServiceThread;
-import com.android.server.appop.AppOpsService;
-import com.android.server.testables.TestableDeviceConfig;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-import java.io.File;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests for {@link AppCompactor}.
- *
- * Build/Install/Run:
- * atest FrameworksMockingServicesTests:AppCompactorTest
- */
-@Presubmit
-@RunWith(MockitoJUnitRunner.class)
-public final class AppCompactorTest {
-
-    private ServiceThread mThread;
-
-    @Mock
-    private AppOpsService mAppOpsService;
-    private AppCompactor mCompactorUnderTest;
-    private HandlerThread mHandlerThread;
-    private Handler mHandler;
-    private CountDownLatch mCountDown;
-
-    @Rule
-    public TestableDeviceConfig.TestableDeviceConfigRule
-            mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
-
-    @Before
-    public void setUp() {
-        mHandlerThread = new HandlerThread("");
-        mHandlerThread.start();
-        mHandler = new Handler(mHandlerThread.getLooper());
-
-        mThread = new ServiceThread("TestServiceThread", Process.THREAD_PRIORITY_DEFAULT,
-                true /* allowIo */);
-        mThread.start();
-
-        ActivityManagerService ams = new ActivityManagerService(
-                new TestInjector(InstrumentationRegistry.getInstrumentation().getContext()),
-                mThread);
-        mCompactorUnderTest = new AppCompactor(ams,
-                new AppCompactor.PropertyChangedCallbackForTest() {
-                    @Override
-                    public void onPropertyChanged() {
-                        if (mCountDown != null) {
-                            mCountDown.countDown();
-                        }
-                    }
-                });
-    }
-
-    @After
-    public void tearDown() {
-        mHandlerThread.quit();
-        mThread.quit();
-        mCountDown = null;
-    }
-
-    @Test
-    public void init_setsDefaults() {
-        mCompactorUnderTest.init();
-        assertThat(mCompactorUnderTest.useCompaction()).isEqualTo(
-                AppCompactor.DEFAULT_USE_COMPACTION);
-        assertThat(mCompactorUnderTest.mCompactActionSome).isEqualTo(
-                compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_1));
-        assertThat(mCompactorUnderTest.mCompactActionFull).isEqualTo(
-                compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_2));
-        assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_1);
-        assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_2);
-        assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
-        assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
-        assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo(
-                AppCompactor.DEFAULT_STATSD_SAMPLE_RATE);
-        assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
-        assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
-        assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
-        assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
-        assertThat(mCompactorUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);
-
-        Set<Integer> expected = new HashSet<>();
-        for (String s : TextUtils.split(AppCompactor.DEFAULT_COMPACT_PROC_STATE_THROTTLE, ",")) {
-            expected.add(Integer.parseInt(s));
-        }
-        assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected);
-    }
-
-    @Test
-    public void init_withDeviceConfigSetsParameters() {
-        // When the DeviceConfig already has a flag value stored (note this test will need to
-        // change if the default value changes from false).
-        assertThat(AppCompactor.DEFAULT_USE_COMPACTION).isFalse();
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_USE_COMPACTION, "true", false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_ACTION_1,
-                Integer.toString((AppCompactor.DEFAULT_COMPACT_ACTION_1 + 1 % 4) + 1), false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_ACTION_2,
-                Integer.toString((AppCompactor.DEFAULT_COMPACT_ACTION_2 + 1 % 4) + 1), false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_THROTTLE_1,
-                Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1), false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_THROTTLE_2,
-                Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1), false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_THROTTLE_3,
-                Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1), false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_THROTTLE_4,
-                Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1), false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_THROTTLE_5,
-                Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_5 + 1), false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_THROTTLE_6,
-                Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_6 + 1), false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_STATSD_SAMPLE_RATE,
-                Float.toString(AppCompactor.DEFAULT_STATSD_SAMPLE_RATE + 0.1f), false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_FULL_RSS_THROTTLE_KB,
-                Long.toString(AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1), false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB,
-                Long.toString(AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1), false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "1,2,3", false);
-
-        // Then calling init will read and set that flag.
-        mCompactorUnderTest.init();
-        assertThat(mCompactorUnderTest.useCompaction()).isTrue();
-        assertThat(mCompactorUnderTest.mCompactionThread.isAlive()).isTrue();
-
-        assertThat(mCompactorUnderTest.mCompactActionSome).isEqualTo(
-                compactActionIntToString((AppCompactor.DEFAULT_COMPACT_ACTION_1 + 1 % 4) + 1));
-        assertThat(mCompactorUnderTest.mCompactActionFull).isEqualTo(
-                compactActionIntToString((AppCompactor.DEFAULT_COMPACT_ACTION_2 + 1 % 4) + 1));
-        assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1);
-        assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1);
-        assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1);
-        assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1);
-        assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_5 + 1);
-        assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_6 + 1);
-        assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo(
-                AppCompactor.DEFAULT_STATSD_SAMPLE_RATE + 0.1f);
-        assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_5 + 1);
-        assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_6 + 1);
-        assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1);
-        assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactly(1, 2, 3);
-    }
-
-    @Test
-    public void useCompaction_listensToDeviceConfigChanges() throws InterruptedException {
-        assertThat(mCompactorUnderTest.useCompaction()).isEqualTo(
-                AppCompactor.DEFAULT_USE_COMPACTION);
-        // When we call init and change some the flag value...
-        mCompactorUnderTest.init();
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_USE_COMPACTION, "true", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
-        // Then that new flag value is updated in the implementation.
-        assertThat(mCompactorUnderTest.useCompaction()).isTrue();
-        assertThat(mCompactorUnderTest.mCompactionThread.isAlive()).isTrue();
-
-        // And again, setting the flag the other way.
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_USE_COMPACTION, "false", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-        assertThat(mCompactorUnderTest.useCompaction()).isFalse();
-    }
-
-    @Test
-    public void useCompaction_listensToDeviceConfigChangesBadValues() throws InterruptedException {
-        assertThat(mCompactorUnderTest.useCompaction()).isEqualTo(
-                AppCompactor.DEFAULT_USE_COMPACTION);
-        mCompactorUnderTest.init();
-
-        // When we push an invalid flag value...
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_USE_COMPACTION, "foobar", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
-        // Then we set the default.
-        assertThat(mCompactorUnderTest.useCompaction()).isEqualTo(
-                AppCompactor.DEFAULT_USE_COMPACTION);
-    }
-
-    @Test
-    public void compactAction_listensToDeviceConfigChanges() throws InterruptedException {
-        mCompactorUnderTest.init();
-
-        // When we override new values for the compaction action with reasonable values...
-
-        // There are four possible values for compactAction[Some|Full].
-        for (int i = 1; i < 5; i++) {
-            mCountDown = new CountDownLatch(2);
-            int expectedSome = (AppCompactor.DEFAULT_COMPACT_ACTION_1 + i) % 4 + 1;
-            DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                    AppCompactor.KEY_COMPACT_ACTION_1, Integer.toString(expectedSome), false);
-            int expectedFull = (AppCompactor.DEFAULT_COMPACT_ACTION_2 + i) % 4 + 1;
-            DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                    AppCompactor.KEY_COMPACT_ACTION_2, Integer.toString(expectedFull), false);
-            assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
-            // Then the updates are reflected in the flags.
-            assertThat(mCompactorUnderTest.mCompactActionSome).isEqualTo(
-                    compactActionIntToString(expectedSome));
-            assertThat(mCompactorUnderTest.mCompactActionFull).isEqualTo(
-                    compactActionIntToString(expectedFull));
-        }
-    }
-
-    @Test
-    public void compactAction_listensToDeviceConfigChangesBadValues() throws InterruptedException {
-        mCompactorUnderTest.init();
-
-        // When we override new values for the compaction action with bad values ...
-        mCountDown = new CountDownLatch(2);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_ACTION_1, "foo", false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_ACTION_2, "foo", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
-        // Then the default values are reflected in the flag
-        assertThat(mCompactorUnderTest.mCompactActionSome).isEqualTo(
-                compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_1));
-        assertThat(mCompactorUnderTest.mCompactActionFull).isEqualTo(
-                compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_2));
-
-        mCountDown = new CountDownLatch(2);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_ACTION_1, "", false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_ACTION_2, "", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
-        assertThat(mCompactorUnderTest.mCompactActionSome).isEqualTo(
-                compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_1));
-        assertThat(mCompactorUnderTest.mCompactActionFull).isEqualTo(
-                compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_2));
-    }
-
-    @Test
-    public void compactThrottle_listensToDeviceConfigChanges() throws InterruptedException {
-        mCompactorUnderTest.init();
-
-        // When we override new reasonable throttle values after init...
-        mCountDown = new CountDownLatch(6);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_THROTTLE_1,
-                Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1), false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_THROTTLE_2,
-                Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1), false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_THROTTLE_3,
-                Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1), false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_THROTTLE_4,
-                Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1), false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_THROTTLE_5,
-                Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_5 + 1), false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_THROTTLE_6,
-                Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_6 + 1), false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
-        // Then those flags values are reflected in the compactor.
-        assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1);
-        assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1);
-        assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1);
-        assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1);
-        assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_5 + 1);
-        assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_6 + 1);
-    }
-
-    @Test
-    public void compactThrottle_listensToDeviceConfigChangesBadValues()
-            throws InterruptedException {
-        mCompactorUnderTest.init();
-
-        // When one of the throttles is overridden with a bad value...
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_THROTTLE_1, "foo", false);
-        // Then all the throttles have the defaults set.
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-        assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_1);
-        assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_2);
-        assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
-        assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
-        assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
-        assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
-
-        // Repeat for each of the throttle keys.
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_THROTTLE_2, "foo", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-        assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_1);
-        assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_2);
-        assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
-        assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
-        assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
-        assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
-
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_THROTTLE_3, "foo", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-        assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_1);
-        assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_2);
-        assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
-        assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
-        assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
-        assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
-
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_THROTTLE_4, "foo", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-        assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_1);
-        assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_2);
-        assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
-        assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
-        assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
-        assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
-
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_THROTTLE_5, "foo", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-        assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_1);
-        assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_2);
-        assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
-        assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
-        assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
-        assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
-
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_THROTTLE_6, "foo", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-        assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_1);
-        assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_2);
-        assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
-        assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
-        assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
-        assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
-    }
-
-    @Test
-    public void statsdSampleRate_listensToDeviceConfigChanges() throws InterruptedException {
-        mCompactorUnderTest.init();
-
-        // When we override mStatsdSampleRate with a reasonable value ...
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_STATSD_SAMPLE_RATE,
-                Float.toString(AppCompactor.DEFAULT_STATSD_SAMPLE_RATE + 0.1f), false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
-        // Then that override is reflected in the compactor.
-        assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo(
-                AppCompactor.DEFAULT_STATSD_SAMPLE_RATE + 0.1f);
-    }
-
-    @Test
-    public void statsdSampleRate_listensToDeviceConfigChangesBadValues()
-            throws InterruptedException {
-        mCompactorUnderTest.init();
-
-        // When we override mStatsdSampleRate with an unreasonable value ...
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_STATSD_SAMPLE_RATE, "foo", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
-        // Then that override is reflected in the compactor.
-        assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo(
-                AppCompactor.DEFAULT_STATSD_SAMPLE_RATE);
-    }
-
-    @Test
-    public void statsdSampleRate_listensToDeviceConfigChangesOutOfRangeValues()
-            throws InterruptedException {
-        mCompactorUnderTest.init();
-
-        // When we override mStatsdSampleRate with an value outside of [0..1]...
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_STATSD_SAMPLE_RATE,
-                Float.toString(-1.0f), false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
-        // Then the values is capped in the range.
-        assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo(0.0f);
-
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_STATSD_SAMPLE_RATE,
-                Float.toString(1.01f), false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
-        // Then the values is capped in the range.
-        assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo(1.0f);
-    }
-
-    @Test
-    public void fullCompactionRssThrottleKb_listensToDeviceConfigChanges()
-            throws InterruptedException {
-        mCompactorUnderTest.init();
-
-        // When we override mStatsdSampleRate with a reasonable value ...
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_FULL_RSS_THROTTLE_KB,
-                Long.toString(AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1), false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
-        // Then that override is reflected in the compactor.
-        assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1);
-    }
-
-    @Test
-    public void fullCompactionRssThrottleKb_listensToDeviceConfigChangesBadValues()
-            throws InterruptedException {
-        mCompactorUnderTest.init();
-
-        // When we override mStatsdSampleRate with an unreasonable value ...
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_FULL_RSS_THROTTLE_KB, "foo", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
-        // Then that override is reflected in the compactor.
-        assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
-
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_FULL_RSS_THROTTLE_KB, "-100", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
-        // Then that override is reflected in the compactor.
-        assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
-    }
-
-    @Test
-    public void fullCompactionDeltaRssThrottleKb_listensToDeviceConfigChanges()
-            throws InterruptedException {
-        mCompactorUnderTest.init();
-
-        // When we override mStatsdSampleRate with a reasonable value ...
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB,
-                Long.toString(AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1), false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
-        // Then that override is reflected in the compactor.
-        assertThat(mCompactorUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1);
-    }
-
-    @Test
-    public void fullCompactionDeltaRssThrottleKb_listensToDeviceConfigChangesBadValues()
-            throws InterruptedException {
-        mCompactorUnderTest.init();
-
-        // When we override mStatsdSampleRate with an unreasonable value ...
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, "foo", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
-        // Then that override is reflected in the compactor.
-        assertThat(mCompactorUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);
-
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, "-100", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
-        // Then that override is reflected in the compactor.
-        assertThat(mCompactorUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);
-    }
-
-    @Test
-    public void procStateThrottle_listensToDeviceConfigChanges()
-            throws InterruptedException {
-        mCompactorUnderTest.init();
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "1,2,3", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-        assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactly(1, 2, 3);
-
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-        assertThat(mCompactorUnderTest.mProcStateThrottle).isEmpty();
-    }
-
-    @Test
-    public void procStateThrottle_listensToDeviceConfigChangesBadValues()
-            throws InterruptedException {
-        mCompactorUnderTest.init();
-
-        Set<Integer> expected = new HashSet<>();
-        for (String s : TextUtils.split(AppCompactor.DEFAULT_COMPACT_PROC_STATE_THROTTLE, ",")) {
-            expected.add(Integer.parseInt(s));
-        }
-
-        // Not numbers
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "foo", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-        assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected);
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "1,foo", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-        assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected);
-
-        // Empty splits
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, ",", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-        assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected);
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, ",,3", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-        assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected);
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "1,,3", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-        assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected);
-    }
-
-    private class TestInjector extends Injector {
-
-        TestInjector(Context context) {
-            super(context);
-        }
-
-        @Override
-        public AppOpsService getAppOpsService(File file, Handler handler) {
-            return mAppOpsService;
-        }
-
-        @Override
-        public Handler getUiHandler(ActivityManagerService service) {
-            return mHandler;
-        }
-    }
-}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
new file mode 100644
index 0000000..f037692
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
@@ -0,0 +1,692 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.ActivityManagerService.Injector;
+import static com.android.server.am.CachedAppOptimizer.compactActionIntToString;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.platform.test.annotations.Presubmit;
+import android.provider.DeviceConfig;
+import android.text.TextUtils;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.ServiceThread;
+import com.android.server.appop.AppOpsService;
+import com.android.server.testables.TestableDeviceConfig;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.io.File;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for {@link CachedAppOptimizer}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksMockingServicesTests:CachedAppOptimizerTest
+ */
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public final class CachedAppOptimizerTest {
+
+    private ServiceThread mThread;
+
+    @Mock
+    private AppOpsService mAppOpsService;
+    private CachedAppOptimizer mCachedAppOptimizerUnderTest;
+    private HandlerThread mHandlerThread;
+    private Handler mHandler;
+    private CountDownLatch mCountDown;
+
+    @Rule
+    public TestableDeviceConfig.TestableDeviceConfigRule
+            mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
+
+    @Before
+    public void setUp() {
+        mHandlerThread = new HandlerThread("");
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+
+        mThread = new ServiceThread("TestServiceThread", Process.THREAD_PRIORITY_DEFAULT,
+                true /* allowIo */);
+        mThread.start();
+
+        ActivityManagerService ams = new ActivityManagerService(
+                new TestInjector(InstrumentationRegistry.getInstrumentation().getContext()),
+                mThread);
+        mCachedAppOptimizerUnderTest = new CachedAppOptimizer(ams,
+                new CachedAppOptimizer.PropertyChangedCallbackForTest() {
+                    @Override
+                    public void onPropertyChanged() {
+                        if (mCountDown != null) {
+                            mCountDown.countDown();
+                        }
+                    }
+                });
+    }
+
+    @After
+    public void tearDown() {
+        mHandlerThread.quit();
+        mThread.quit();
+        mCountDown = null;
+    }
+
+    @Test
+    public void init_setsDefaults() {
+        mCachedAppOptimizerUnderTest.init();
+        assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isEqualTo(
+                CachedAppOptimizer.DEFAULT_USE_COMPACTION);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome).isEqualTo(
+                compactActionIntToString(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1));
+        assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull).isEqualTo(
+                compactActionIntToString(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2));
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
+        assertThat(mCachedAppOptimizerUnderTest.mStatsdSampleRate).isEqualTo(
+                CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE);
+        assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
+        assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
+        assertThat(mCachedAppOptimizerUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);
+
+        Set<Integer> expected = new HashSet<>();
+        for (String s : TextUtils.split(
+                CachedAppOptimizer.DEFAULT_COMPACT_PROC_STATE_THROTTLE, ",")) {
+            expected.add(Integer.parseInt(s));
+        }
+        assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle)
+                .containsExactlyElementsIn(expected);
+    }
+
+    @Test
+    public void init_withDeviceConfigSetsParameters() {
+        // When the DeviceConfig already has a flag value stored (note this test will need to
+        // change if the default value changes from false).
+        assertThat(CachedAppOptimizer.DEFAULT_USE_COMPACTION).isFalse();
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_USE_COMPACTION, "true", false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_ACTION_1,
+                Integer.toString((CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1 + 1 % 4) + 1), false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_ACTION_2,
+                Integer.toString((CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2 + 1 % 4) + 1), false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_THROTTLE_1,
+                Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1 + 1), false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_THROTTLE_2,
+                Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2 + 1), false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_THROTTLE_3,
+                Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3 + 1), false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_THROTTLE_4,
+                Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4 + 1), false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_THROTTLE_5,
+                Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1), false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_THROTTLE_6,
+                Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1), false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_STATSD_SAMPLE_RATE,
+                Float.toString(CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE + 0.1f), false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_FULL_RSS_THROTTLE_KB,
+                Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1), false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB,
+                Long.toString(
+                        CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1), false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, "1,2,3", false);
+
+        // Then calling init will read and set that flag.
+        mCachedAppOptimizerUnderTest.init();
+        assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isTrue();
+        assertThat(mCachedAppOptimizerUnderTest.mCachedAppOptimizerThread.isAlive()).isTrue();
+
+        assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome).isEqualTo(
+                compactActionIntToString(
+                        (CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1 + 1 % 4) + 1));
+        assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull).isEqualTo(
+                compactActionIntToString(
+                        (CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2 + 1 % 4) + 1));
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1 + 1);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2 + 1);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3 + 1);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4 + 1);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1);
+        assertThat(mCachedAppOptimizerUnderTest.mStatsdSampleRate).isEqualTo(
+                CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE + 0.1f);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1);
+        assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1);
+        assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle).containsExactly(1, 2, 3);
+    }
+
+    @Test
+    public void useCompaction_listensToDeviceConfigChanges() throws InterruptedException {
+        assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isEqualTo(
+                CachedAppOptimizer.DEFAULT_USE_COMPACTION);
+        // When we call init and change some the flag value...
+        mCachedAppOptimizerUnderTest.init();
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_USE_COMPACTION, "true", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+        // Then that new flag value is updated in the implementation.
+        assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isTrue();
+        assertThat(mCachedAppOptimizerUnderTest.mCachedAppOptimizerThread.isAlive()).isTrue();
+
+        // And again, setting the flag the other way.
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_USE_COMPACTION, "false", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isFalse();
+    }
+
+    @Test
+    public void useCompaction_listensToDeviceConfigChangesBadValues() throws InterruptedException {
+        assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isEqualTo(
+                CachedAppOptimizer.DEFAULT_USE_COMPACTION);
+        mCachedAppOptimizerUnderTest.init();
+
+        // When we push an invalid flag value...
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_USE_COMPACTION, "foobar", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+        // Then we set the default.
+        assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isEqualTo(
+                CachedAppOptimizer.DEFAULT_USE_COMPACTION);
+    }
+
+    @Test
+    public void compactAction_listensToDeviceConfigChanges() throws InterruptedException {
+        mCachedAppOptimizerUnderTest.init();
+
+        // When we override new values for the compaction action with reasonable values...
+
+        // There are four possible values for compactAction[Some|Full].
+        for (int i = 1; i < 5; i++) {
+            mCountDown = new CountDownLatch(2);
+            int expectedSome = (CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1 + i) % 4 + 1;
+            DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    CachedAppOptimizer.KEY_COMPACT_ACTION_1, Integer.toString(expectedSome), false);
+            int expectedFull = (CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2 + i) % 4 + 1;
+            DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    CachedAppOptimizer.KEY_COMPACT_ACTION_2, Integer.toString(expectedFull), false);
+            assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+            // Then the updates are reflected in the flags.
+            assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome).isEqualTo(
+                    compactActionIntToString(expectedSome));
+            assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull).isEqualTo(
+                    compactActionIntToString(expectedFull));
+        }
+    }
+
+    @Test
+    public void compactAction_listensToDeviceConfigChangesBadValues() throws InterruptedException {
+        mCachedAppOptimizerUnderTest.init();
+
+        // When we override new values for the compaction action with bad values ...
+        mCountDown = new CountDownLatch(2);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_ACTION_1, "foo", false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_ACTION_2, "foo", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+        // Then the default values are reflected in the flag
+        assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome).isEqualTo(
+                compactActionIntToString(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1));
+        assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull).isEqualTo(
+                compactActionIntToString(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2));
+
+        mCountDown = new CountDownLatch(2);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_ACTION_1, "", false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_ACTION_2, "", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+        assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome).isEqualTo(
+                compactActionIntToString(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1));
+        assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull).isEqualTo(
+                compactActionIntToString(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2));
+    }
+
+    @Test
+    public void compactThrottle_listensToDeviceConfigChanges() throws InterruptedException {
+        mCachedAppOptimizerUnderTest.init();
+
+        // When we override new reasonable throttle values after init...
+        mCountDown = new CountDownLatch(6);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_THROTTLE_1,
+                Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1 + 1), false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_THROTTLE_2,
+                Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2 + 1), false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_THROTTLE_3,
+                Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3 + 1), false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_THROTTLE_4,
+                Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4 + 1), false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_THROTTLE_5,
+                Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1), false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_THROTTLE_6,
+                Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1), false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+        // Then those flags values are reflected in the compactor.
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1 + 1);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2 + 1);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3 + 1);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4 + 1);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1);
+    }
+
+    @Test
+    public void compactThrottle_listensToDeviceConfigChangesBadValues()
+            throws InterruptedException {
+        mCachedAppOptimizerUnderTest.init();
+
+        // When one of the throttles is overridden with a bad value...
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_THROTTLE_1, "foo", false);
+        // Then all the throttles have the defaults set.
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
+
+        // Repeat for each of the throttle keys.
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_THROTTLE_2, "foo", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
+
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_THROTTLE_3, "foo", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
+
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_THROTTLE_4, "foo", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
+
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_THROTTLE_5, "foo", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
+
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_THROTTLE_6, "foo", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
+    }
+
+    @Test
+    public void statsdSampleRate_listensToDeviceConfigChanges() throws InterruptedException {
+        mCachedAppOptimizerUnderTest.init();
+
+        // When we override mStatsdSampleRate with a reasonable value ...
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_STATSD_SAMPLE_RATE,
+                Float.toString(CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE + 0.1f), false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+        // Then that override is reflected in the compactor.
+        assertThat(mCachedAppOptimizerUnderTest.mStatsdSampleRate).isEqualTo(
+                CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE + 0.1f);
+    }
+
+    @Test
+    public void statsdSampleRate_listensToDeviceConfigChangesBadValues()
+            throws InterruptedException {
+        mCachedAppOptimizerUnderTest.init();
+
+        // When we override mStatsdSampleRate with an unreasonable value ...
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_STATSD_SAMPLE_RATE, "foo", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+        // Then that override is reflected in the compactor.
+        assertThat(mCachedAppOptimizerUnderTest.mStatsdSampleRate).isEqualTo(
+                CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE);
+    }
+
+    @Test
+    public void statsdSampleRate_listensToDeviceConfigChangesOutOfRangeValues()
+            throws InterruptedException {
+        mCachedAppOptimizerUnderTest.init();
+
+        // When we override mStatsdSampleRate with an value outside of [0..1]...
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_STATSD_SAMPLE_RATE,
+                Float.toString(-1.0f), false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+        // Then the values is capped in the range.
+        assertThat(mCachedAppOptimizerUnderTest.mStatsdSampleRate).isEqualTo(0.0f);
+
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_STATSD_SAMPLE_RATE,
+                Float.toString(1.01f), false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+        // Then the values is capped in the range.
+        assertThat(mCachedAppOptimizerUnderTest.mStatsdSampleRate).isEqualTo(1.0f);
+    }
+
+    @Test
+    public void fullCompactionRssThrottleKb_listensToDeviceConfigChanges()
+            throws InterruptedException {
+        mCachedAppOptimizerUnderTest.init();
+
+        // When we override mStatsdSampleRate with a reasonable value ...
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_FULL_RSS_THROTTLE_KB,
+                Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1), false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+        // Then that override is reflected in the compactor.
+        assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1);
+    }
+
+    @Test
+    public void fullCompactionRssThrottleKb_listensToDeviceConfigChangesBadValues()
+            throws InterruptedException {
+        mCachedAppOptimizerUnderTest.init();
+
+        // When we override mStatsdSampleRate with an unreasonable value ...
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_FULL_RSS_THROTTLE_KB, "foo", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+        // Then that override is reflected in the compactor.
+        assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
+
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_FULL_RSS_THROTTLE_KB, "-100", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+        // Then that override is reflected in the compactor.
+        assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
+    }
+
+    @Test
+    public void fullCompactionDeltaRssThrottleKb_listensToDeviceConfigChanges()
+            throws InterruptedException {
+        mCachedAppOptimizerUnderTest.init();
+
+        // When we override mStatsdSampleRate with a reasonable value ...
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB,
+                Long.toString(
+                        CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1), false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+        // Then that override is reflected in the compactor.
+        assertThat(mCachedAppOptimizerUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1);
+    }
+
+    @Test
+    public void fullCompactionDeltaRssThrottleKb_listensToDeviceConfigChangesBadValues()
+            throws InterruptedException {
+        mCachedAppOptimizerUnderTest.init();
+
+        // When we override mStatsdSampleRate with an unreasonable value ...
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, "foo", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+        // Then that override is reflected in the compactor.
+        assertThat(mCachedAppOptimizerUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);
+
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, "-100", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+        // Then that override is reflected in the compactor.
+        assertThat(mCachedAppOptimizerUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);
+    }
+
+    @Test
+    public void procStateThrottle_listensToDeviceConfigChanges()
+            throws InterruptedException {
+        mCachedAppOptimizerUnderTest.init();
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, "1,2,3", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle).containsExactly(1, 2, 3);
+
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, "", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle).isEmpty();
+    }
+
+    @Test
+    public void procStateThrottle_listensToDeviceConfigChangesBadValues()
+            throws InterruptedException {
+        mCachedAppOptimizerUnderTest.init();
+
+        Set<Integer> expected = new HashSet<>();
+        for (String s : TextUtils.split(
+                CachedAppOptimizer.DEFAULT_COMPACT_PROC_STATE_THROTTLE, ",")) {
+            expected.add(Integer.parseInt(s));
+        }
+
+        // Not numbers
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, "foo", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle)
+                .containsExactlyElementsIn(expected);
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, "1,foo", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle)
+                .containsExactlyElementsIn(expected);
+
+        // Empty splits
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, ",", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle)
+                .containsExactlyElementsIn(expected);
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, ",,3", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle)
+                .containsExactlyElementsIn(expected);
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, "1,,3", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle)
+                .containsExactlyElementsIn(expected);
+    }
+
+    private class TestInjector extends Injector {
+
+        TestInjector(Context context) {
+            super(context);
+        }
+
+        @Override
+        public AppOpsService getAppOpsService(File file, Handler handler) {
+            return mAppOpsService;
+        }
+
+        @Override
+        public Handler getUiHandler(ActivityManagerService service) {
+            return mHandler;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
index c223f13..e1e9b7e 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -75,6 +75,7 @@
 import android.os.IPowerManager;
 import android.os.PowerManager;
 import android.os.Process;
+import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.testing.DexmakerShareClassLoaderRule;
 import android.view.Display;
@@ -693,6 +694,18 @@
         assertThat(result, is(false));
     }
 
+    @Test
+    public void takeScreenshot_returnNull() {
+        // no canTakeScreenshot, should return null.
+        when(mMockSecurityPolicy.canTakeScreenshotLocked(mServiceConnection)).thenReturn(false);
+        assertThat(mServiceConnection.takeScreenshot(Display.DEFAULT_DISPLAY), is(nullValue()));
+
+        // no checkAccessibilityAccess, should return null.
+        when(mMockSecurityPolicy.canTakeScreenshotLocked(mServiceConnection)).thenReturn(true);
+        when(mMockSecurityPolicy.checkAccessibilityAccess(mServiceConnection)).thenReturn(false);
+        assertThat(mServiceConnection.takeScreenshot(Display.DEFAULT_DISPLAY), is(nullValue()));
+    }
+
     private void updateServiceInfo(AccessibilityServiceInfo serviceInfo, int eventType,
             int feedbackType, int flags, String[] packageNames, int notificationTimeout) {
         serviceInfo.eventTypes = eventType;
@@ -832,5 +845,8 @@
 
         @Override
         public void onFingerprintGesture(int gesture) {}
+
+        @Override
+        public void takeScreenshotWithCallback(int displayId, RemoteCallback callback) {}
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java
index 04ac7fe..1504097 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java
@@ -346,6 +346,14 @@
     }
 
     @Test
+    public void canTakeScreenshot_hasCapability_returnTrue() {
+        when(mMockA11yServiceConnection.getCapabilities())
+                .thenReturn(AccessibilityServiceInfo.CAPABILITY_CAN_TAKE_SCREENSHOT);
+
+        assertTrue(mA11ySecurityPolicy.canTakeScreenshotLocked(mMockA11yServiceConnection));
+    }
+
+    @Test
     public void resolveProfileParent_userIdIsCurrentUser_returnCurrentUser() {
         final int currentUserId = 10;
         final int userId = currentUserId;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
index 96d9c47..ac5169c 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
@@ -113,7 +113,6 @@
         mUserState.mAccessibilityButtonTargets.add(COMPONENT_NAME.flattenToString());
         mUserState.setTouchExplorationEnabledLocked(true);
         mUserState.setDisplayMagnificationEnabledLocked(true);
-        mUserState.setNavBarMagnificationEnabledLocked(true);
         mUserState.setAutoclickEnabledLocked(true);
         mUserState.setUserNonInteractiveUiTimeoutLocked(30);
         mUserState.setUserInteractiveUiTimeoutLocked(30);
@@ -132,7 +131,6 @@
         assertTrue(mUserState.mAccessibilityButtonTargets.isEmpty());
         assertFalse(mUserState.isTouchExplorationEnabledLocked());
         assertFalse(mUserState.isDisplayMagnificationEnabledLocked());
-        assertFalse(mUserState.isNavBarMagnificationEnabledLocked());
         assertFalse(mUserState.isAutoclickEnabledLocked());
         assertEquals(0, mUserState.getUserNonInteractiveUiTimeoutLocked());
         assertEquals(0, mUserState.getUserInteractiveUiTimeoutLocked());
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 79cc3db..f122014 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -31,6 +31,7 @@
 
 import static com.google.android.collect.Lists.newArrayList;
 import static com.google.android.collect.Sets.newHashSet;
+import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertEquals;
@@ -54,6 +55,7 @@
 import static org.mockito.Mockito.when;
 
 import android.annotation.UserIdInt;
+import android.app.ActivityManager;
 import android.app.IUserSwitchObserver;
 import android.content.Context;
 import android.content.IIntentReceiver;
@@ -74,10 +76,10 @@
 import android.platform.test.annotations.Presubmit;
 import android.util.Log;
 
-
 import androidx.test.filters.SmallTest;
 
 import com.android.server.FgThread;
+import com.android.server.am.UserState.KeyEvictedCallback;
 import com.android.server.pm.UserManagerService;
 import com.android.server.wm.WindowManagerService;
 
@@ -107,6 +109,7 @@
     private static final int TEST_USER_ID = 100;
     private static final int TEST_USER_ID1 = 101;
     private static final int TEST_USER_ID2 = 102;
+    private static final int TEST_USER_ID3 = 103;
     private static final int NONEXIST_USER_ID = 2;
     private static final int TEST_PRE_CREATED_USER_ID = 103;
 
@@ -120,6 +123,8 @@
     private TestInjector mInjector;
     private final HashMap<Integer, UserState> mUserStates = new HashMap<>();
 
+    private final KeyEvictedCallback mKeyEvictedCallback = (userId) -> { /* ignore */ };
+
     private static final List<String> START_FOREGROUND_USER_ACTIONS = newArrayList(
             Intent.ACTION_USER_STARTED,
             Intent.ACTION_USER_SWITCHED,
@@ -153,6 +158,7 @@
             doNothing().when(mInjector).activityManagerOnUserStopped(anyInt());
             doNothing().when(mInjector).clearBroadcastQueueForUser(anyInt());
             doNothing().when(mInjector).stackSupervisorRemoveUser(anyInt());
+            // All UserController params are set to default.
             mUserController = new UserController(mInjector);
             setUpUser(TEST_USER_ID, NO_USERINFO_FLAGS);
             setUpUser(TEST_PRE_CREATED_USER_ID, NO_USERINFO_FLAGS, /* preCreated=*/ true);
@@ -187,7 +193,9 @@
 
     @Test
     public void testStartUserUIDisabled() {
-        mUserController.mUserSwitchUiEnabled = false;
+        mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false,
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+
         mUserController.startUser(TEST_USER_ID, true /* foreground */);
         verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
         verify(mInjector.getWindowManager(), never()).stopFreezingScreen();
@@ -245,7 +253,9 @@
 
     @Test
     public void testFailedStartUserInForeground() {
-        mUserController.mUserSwitchUiEnabled = false;
+        mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false,
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+
         mUserController.startUserInForeground(NONEXIST_USER_ID);
         verify(mInjector.getWindowManager(), times(1)).setSwitchingUser(anyBoolean());
         verify(mInjector.getWindowManager()).setSwitchingUser(false);
@@ -326,7 +336,9 @@
 
     @Test
     public void testContinueUserSwitchUIDisabled() throws RemoteException {
-        mUserController.mUserSwitchUiEnabled = false;
+        mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false,
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+
         // Start user -- this will update state of mUserController
         mUserController.startUser(TEST_USER_ID, true);
         Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
@@ -389,11 +401,13 @@
      * Test stopping of user from max running users limit.
      */
     @Test
-    public void testUserStoppingForMultipleUsersNormalMode()
+    public void testUserLockingFromUserSwitchingForMultipleUsersNonDelayedLocking()
             throws InterruptedException, RemoteException {
+        mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+
         setUpUser(TEST_USER_ID1, 0);
         setUpUser(TEST_USER_ID2, 0);
-        mUserController.mMaxRunningUsers = 3;
         int numerOfUserSwitches = 1;
         addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM,
                 numerOfUserSwitches, false);
@@ -430,12 +444,13 @@
      * all middle steps which takes too much work to mock.
      */
     @Test
-    public void testUserStoppingForMultipleUsersDelayedLockingMode()
+    public void testUserLockingFromUserSwitchingForMultipleUsersDelayedLockingMode()
             throws InterruptedException, RemoteException {
+        mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true);
+
         setUpUser(TEST_USER_ID1, 0);
         setUpUser(TEST_USER_ID2, 0);
-        mUserController.mMaxRunningUsers = 3;
-        mUserController.mDelayUserDataLocking = true;
         int numerOfUserSwitches = 1;
         addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM,
                 numerOfUserSwitches, false);
@@ -456,7 +471,7 @@
         // Skip all other steps and test unlock delaying only
         UserState uss = mUserStates.get(TEST_USER_ID);
         uss.setState(UserState.STATE_SHUTDOWN); // necessary state change from skipped part
-        mUserController.finishUserStopped(uss);
+        mUserController.finishUserStopped(uss, /* allowDelayedLocking= */ true);
         // Cannot mock FgThread handler, so confirm that there is no posted message left before
         // checking.
         waitForHandlerToComplete(FgThread.getHandler(), HANDLER_WAIT_TIME_MS);
@@ -473,12 +488,87 @@
                 mUserController.getRunningUsersLU());
         UserState ussUser1 = mUserStates.get(TEST_USER_ID1);
         ussUser1.setState(UserState.STATE_SHUTDOWN);
-        mUserController.finishUserStopped(ussUser1);
+        mUserController.finishUserStopped(ussUser1, /* allowDelayedLocking= */ true);
         waitForHandlerToComplete(FgThread.getHandler(), HANDLER_WAIT_TIME_MS);
         verify(mInjector.mStorageManagerMock, times(1))
                 .lockUserKey(TEST_USER_ID);
     }
 
+    /**
+     * Test locking user with mDelayUserDataLocking false.
+     */
+    @Test
+    public void testUserLockingWithStopUserForNonDelayedLockingMode() throws Exception {
+        mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+
+        setUpAndStartUserInBackground(TEST_USER_ID);
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID, /* delayedLocking= */ true,
+                /* keyEvictedCallback= */ null, /* expectLocking= */ true);
+
+        setUpAndStartUserInBackground(TEST_USER_ID1);
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
+                /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
+
+        setUpAndStartUserInBackground(TEST_USER_ID2);
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* delayedLocking= */ false,
+                /* keyEvictedCallback= */ null, /* expectLocking= */ true);
+
+        setUpAndStartUserInBackground(TEST_USER_ID3);
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID3, /* delayedLocking= */ false,
+                /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
+    }
+
+    /**
+     * Test conditional delayed locking with mDelayUserDataLocking true.
+     */
+    @Test
+    public void testUserLockingForDelayedLockingMode() throws Exception {
+        mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true);
+
+        // delayedLocking set and no KeyEvictedCallback, so it should not lock.
+        setUpAndStartUserInBackground(TEST_USER_ID);
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID, /* delayedLocking= */ true,
+                /* keyEvictedCallback= */ null, /* expectLocking= */ false);
+
+        setUpAndStartUserInBackground(TEST_USER_ID1);
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
+                /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
+
+        setUpAndStartUserInBackground(TEST_USER_ID2);
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* delayedLocking= */ false,
+                /* keyEvictedCallback= */ null, /* expectLocking= */ true);
+
+        setUpAndStartUserInBackground(TEST_USER_ID3);
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID3, /* delayedLocking= */ false,
+                /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
+    }
+
+    private void setUpAndStartUserInBackground(int userId) throws Exception {
+        setUpUser(userId, 0);
+        mUserController.startUser(userId, /* foreground= */ false);
+        verify(mInjector.mStorageManagerMock, times(1))
+                .unlockUserKey(TEST_USER_ID, 0, null, null);
+        mUserStates.put(userId, mUserController.getStartedUserState(userId));
+    }
+
+    private void assertUserLockedOrUnlockedAfterStopping(int userId, boolean delayedLocking,
+            KeyEvictedCallback keyEvictedCallback, boolean expectLocking)  throws Exception {
+        int r = mUserController.stopUser(userId, /* force= */ true, /* delayedLocking= */
+                delayedLocking, null, keyEvictedCallback);
+        assertThat(r).isEqualTo(ActivityManager.USER_OP_SUCCESS);
+        // fake all interim steps
+        UserState ussUser = mUserStates.get(userId);
+        ussUser.setState(UserState.STATE_SHUTDOWN);
+        // Passing delayedLocking invalidates incorrect internal data passing but currently there is
+        // no easy way to get that information passed through lambda.
+        mUserController.finishUserStopped(ussUser, delayedLocking);
+        waitForHandlerToComplete(FgThread.getHandler(), HANDLER_WAIT_TIME_MS);
+        verify(mInjector.mStorageManagerMock, times(expectLocking ? 1 : 0))
+                .lockUserKey(userId);
+    }
+
     private void addForegroundUserAndContinueUserSwitch(int newUserId, int expectedOldUserId,
             int expectedNumberOfCalls, boolean expectOldUserStopping)
             throws RemoteException {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index f96d996..bec265e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -411,7 +411,8 @@
         // HAT sent to keystore
         verify(mBiometricService.mKeyStore).addAuthToken(any(byte[].class));
         // Send onAuthenticated to client
-        verify(mReceiver1).onAuthenticationSucceeded();
+        verify(mReceiver1).onAuthenticationSucceeded(
+                BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC);
         // Current session becomes null
         assertNull(mBiometricService.mCurrentAuthSession);
     }
@@ -461,7 +462,8 @@
                 BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED);
         waitForIdle();
         verify(mBiometricService.mKeyStore).addAuthToken(any(byte[].class));
-        verify(mReceiver1).onAuthenticationSucceeded();
+        verify(mReceiver1).onAuthenticationSucceeded(
+                BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
index abe39f0..312ff2c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
@@ -223,4 +223,22 @@
                     Utils.biometricConstantsToBiometricManager(testCases[i][0]));
         }
     }
+
+    @Test
+    public void testGetAuthenticationTypeForResult_getsCorrectType() {
+        assertEquals(Utils.getAuthenticationTypeForResult(
+                BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED),
+                BiometricPrompt.AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL);
+        assertEquals(Utils.getAuthenticationTypeForResult(
+                BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED),
+                BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC);
+        assertEquals(Utils.getAuthenticationTypeForResult(
+                BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED),
+                BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetAuthResultType_throwsForInvalidReason() {
+        Utils.getAuthenticationTypeForResult(BiometricPrompt.DISMISSED_REASON_NEGATIVE);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 45729e5..d670361 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -1968,6 +1968,29 @@
         // TODO Make sure restrictions are written to the file.
     }
 
+    private static final Set<String> PROFILE_OWNER_ORGANIZATION_OWNED_GLOBAL_RESTRICTIONS =
+            Sets.newSet(
+                    UserManager.DISALLOW_CONFIG_DATE_TIME,
+                    UserManager.DISALLOW_ADD_USER,
+                    UserManager.DISALLOW_BLUETOOTH,
+                    UserManager.DISALLOW_BLUETOOTH_SHARING,
+                    UserManager.DISALLOW_CONFIG_BLUETOOTH,
+                    UserManager.DISALLOW_CONFIG_CELL_BROADCASTS,
+                    UserManager.DISALLOW_CONFIG_LOCATION,
+                    UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS,
+                    UserManager.DISALLOW_CONFIG_PRIVATE_DNS,
+                    UserManager.DISALLOW_CONFIG_TETHERING,
+                    UserManager.DISALLOW_CONFIG_WIFI,
+                    UserManager.DISALLOW_CONTENT_CAPTURE,
+                    UserManager.DISALLOW_CONTENT_SUGGESTIONS,
+                    UserManager.DISALLOW_DATA_ROAMING,
+                    UserManager.DISALLOW_DEBUGGING_FEATURES,
+                    UserManager.DISALLOW_SAFE_BOOT,
+                    UserManager.DISALLOW_SHARE_LOCATION,
+                    UserManager.DISALLOW_SMS,
+                    UserManager.DISALLOW_USB_FILE_TRANSFER
+            );
+
     public void testSetUserRestriction_asPoOfOrgOwnedDevice() throws Exception {
         final int MANAGED_PROFILE_USER_ID = DpmMockContext.CALLER_USER_HANDLE;
         final int MANAGED_PROFILE_ADMIN_UID =
@@ -1980,15 +2003,9 @@
         when(getServices().userManager.getProfileParent(MANAGED_PROFILE_USER_ID))
                 .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0));
 
-        parentDpm.addUserRestriction(admin1, UserManager.DISALLOW_CONFIG_DATE_TIME);
-        verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
-                eq(MANAGED_PROFILE_USER_ID),
-                MockUtils.checkUserRestrictions(UserManager.DISALLOW_CONFIG_DATE_TIME),
-                eq(UserManagerInternal.OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE));
-        reset(getServices().userManagerInternal);
-
-        parentDpm.clearUserRestriction(admin1, UserManager.DISALLOW_CONFIG_DATE_TIME);
-        reset(getServices().userManagerInternal);
+        for (String restriction : PROFILE_OWNER_ORGANIZATION_OWNED_GLOBAL_RESTRICTIONS) {
+            addAndRemoveUserRestrictionOnParentDpm(restriction);
+        }
 
         parentDpm.setCameraDisabled(admin1, true);
         verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
@@ -2005,6 +2022,20 @@
         reset(getServices().userManagerInternal);
     }
 
+    private void addAndRemoveUserRestrictionOnParentDpm(String restriction) {
+        parentDpm.addUserRestriction(admin1, restriction);
+        verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(DpmMockContext.CALLER_USER_HANDLE),
+                MockUtils.checkUserRestrictions(restriction),
+                eq(UserManagerInternal.OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE));
+        parentDpm.clearUserRestriction(admin1, restriction);
+        DpmTestUtils.assertRestrictions(
+                DpmTestUtils.newRestrictions(),
+                parentDpm.getUserRestrictions(admin1)
+        );
+        reset(getServices().userManagerInternal);
+    }
+
     public void testNoDefaultEnabledUserRestrictions() throws Exception {
         mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
         mContext.callerPermissions.add(permission.MANAGE_USERS);
diff --git a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java
index 7a070ee..eda2b70 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java
@@ -188,7 +188,7 @@
     }
 
     @Test
-    public void testEvaluateRules_ruleNotInDNF_ignoreAndAllow() {
+    public void testEvaluateRules_orRules() {
         CompoundFormula compoundFormula =
                 new CompoundFormula(
                         CompoundFormula.OR,
@@ -206,11 +206,11 @@
         IntegrityCheckResult result =
                 RuleEvaluator.evaluateRules(Collections.singletonList(rule), APP_INSTALL_METADATA);
 
-        assertEquals(ALLOW, result.getEffect());
+        assertEquals(DENY, result.getEffect());
     }
 
     @Test
-    public void testEvaluateRules_compoundFormulaWithNot_allow() {
+    public void testEvaluateRules_compoundFormulaWithNot_deny() {
         CompoundFormula openSubFormula =
                 new CompoundFormula(
                         CompoundFormula.AND,
@@ -230,7 +230,7 @@
         IntegrityCheckResult result =
                 RuleEvaluator.evaluateRules(Collections.singletonList(rule), APP_INSTALL_METADATA);
 
-        assertEquals(ALLOW, result.getEffect());
+        assertEquals(DENY, result.getEffect());
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/integrity/model/ByteTrackedOutputStreamTest.java b/services/tests/servicestests/src/com/android/server/integrity/model/ByteTrackedOutputStreamTest.java
index c7cc343..57274bf 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/model/ByteTrackedOutputStreamTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/model/ByteTrackedOutputStreamTest.java
@@ -53,17 +53,17 @@
         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
         ByteTrackedOutputStream byteTrackedOutputStream = new ByteTrackedOutputStream(outputStream);
 
-        BitOutputStream bitOutputStream = new BitOutputStream();
+        BitOutputStream bitOutputStream = new BitOutputStream(byteTrackedOutputStream);
         bitOutputStream.setNext(/* numOfBits= */5, /* value= */1);
-        byteTrackedOutputStream.write(bitOutputStream.toByteArray());
+        bitOutputStream.flush();
 
         // Even though we wrote 5 bits, this will complete to 1 byte.
         assertThat(byteTrackedOutputStream.getWrittenBytesCount()).isEqualTo(1);
 
         // Add a bit less than 2 bytes (10 bits).
-        bitOutputStream.clear();
         bitOutputStream.setNext(/* numOfBits= */10, /* value= */1);
-        byteTrackedOutputStream.write(bitOutputStream.toByteArray());
+        bitOutputStream.flush();
+        assertThat(byteTrackedOutputStream.getWrittenBytesCount()).isEqualTo(3);
 
         assertThat(outputStream.toByteArray().length).isEqualTo(3);
     }
diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java
index 291cfee..742952e 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java
@@ -115,8 +115,7 @@
                                 + getKeyValueString(START_INDEXING_KEY, 500)
                                 + getKeyValueString(END_INDEXING_KEY, 900)
                                 + getKeyValueString(START_INDEXING_KEY, 900)
-                                + getKeyValueString(END_INDEXING_KEY, 945)
-                                + getBits(1, 1));
+                                + getKeyValueString(END_INDEXING_KEY, 945));
         ByteBuffer rule = ByteBuffer.allocate(stringBytes.length);
         rule.put(stringBytes);
         InputStream inputStream = new ByteArrayInputStream(rule.array());
@@ -153,8 +152,7 @@
                                 + getKeyValueString("888", 800)
                                 + getKeyValueString(END_INDEXING_KEY, 900)
                                 + getKeyValueString(START_INDEXING_KEY, 900)
-                                + getKeyValueString(END_INDEXING_KEY, 945)
-                                + getBits(1, 1));
+                                + getKeyValueString(END_INDEXING_KEY, 945));
         ByteBuffer rule = ByteBuffer.allocate(stringBytes.length);
         rule.put(stringBytes);
         return new ByteArrayInputStream(rule.array());
diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
index bc2aac0..8ee5f5f 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
@@ -139,10 +139,12 @@
                 SERIALIZED_START_INDEXING_KEY
                         + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32)
                         + SERIALIZED_END_INDEXING_KEY
-                        + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */32);
+                        + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32);
         byte[] expectedIndexingBytes =
-                getBytes(serializedIndexingBytes + serializedIndexingBytes
-                        + serializedIndexingBytes + getBits(1, 1));
+                getBytes(
+                        serializedIndexingBytes
+                                + serializedIndexingBytes
+                                + serializedIndexingBytes);
         expectedIndexingOutputStream.write(expectedIndexingBytes);
         assertThat(indexingOutputStream.toByteArray())
                 .isEqualTo(expectedIndexingOutputStream.toByteArray());
@@ -200,11 +202,14 @@
                 SERIALIZED_START_INDEXING_KEY
                         + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32)
                         + SERIALIZED_END_INDEXING_KEY
-                        + getBits(DEFAULT_FORMAT_VERSION_BYTES.length
-                        + getBytes(expectedBits).length, /* numOfBits= */ 32);
-        expectedIndexingOutputStream.write(getBytes(
-                expectedIndexingBitsForIndexed + expectedIndexingBitsForIndexed
-                        + expectedIndexingBitsForUnindexed + getBits(1, 1)));
+                        + getBits(
+                                DEFAULT_FORMAT_VERSION_BYTES.length + getBytes(expectedBits).length,
+                                /* numOfBits= */ 32);
+        expectedIndexingOutputStream.write(
+                getBytes(
+                        expectedIndexingBitsForIndexed
+                                + expectedIndexingBitsForIndexed
+                                + expectedIndexingBitsForUnindexed));
 
         assertThat(indexingOutputStream.toByteArray())
                 .isEqualTo(expectedIndexingOutputStream.toByteArray());
@@ -513,16 +518,19 @@
         // and 225 non-indexed rules..
         List<Rule> ruleList = new ArrayList();
         for (int count = 0; count < ruleCount; count++) {
-            ruleList.add(getRuleWithPackageNameAndSampleInstallerName(
-                    String.format("%s%04d", packagePrefix, count)));
+            ruleList.add(
+                    getRuleWithPackageNameAndSampleInstallerName(
+                            String.format("%s%04d", packagePrefix, count)));
         }
         for (int count = 0; count < ruleCount; count++) {
-            ruleList.add(getRuleWithAppCertificateAndSampleInstallerName(
-                    String.format("%s%04d", appCertificatePrefix, count)));
+            ruleList.add(
+                    getRuleWithAppCertificateAndSampleInstallerName(
+                            String.format("%s%04d", appCertificatePrefix, count)));
         }
         for (int count = 0; count < ruleCount; count++) {
-            ruleList.add(getNonIndexedRuleWithInstallerName(
-                    String.format("%s%04d", installerNamePrefix, count)));
+            ruleList.add(
+                    getNonIndexedRuleWithInstallerName(
+                            String.format("%s%04d", installerNamePrefix, count)));
         }
 
         // Serialize the rules.
@@ -543,8 +551,7 @@
         int totalBytesWritten = DEFAULT_FORMAT_VERSION_BYTES.length;
 
         String expectedIndexingBytesForPackageNameIndexed =
-                SERIALIZED_START_INDEXING_KEY
-                        + getBits(totalBytesWritten, /* numOfBits= */ 32);
+                SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
         for (int count = 0; count < ruleCount; count++) {
             String packageName = String.format("%s%04d", packagePrefix, count);
             if (count > 0 && count % INDEXING_BLOCK_SIZE == 0) {
@@ -556,18 +563,17 @@
             }
 
             byte[] bytesForPackage =
-                    getBytes(getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(
-                            packageName));
+                    getBytes(
+                            getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(
+                                    packageName));
             expectedOrderedRuleOutputStream.write(bytesForPackage);
             totalBytesWritten += bytesForPackage.length;
         }
         expectedIndexingBytesForPackageNameIndexed +=
-                SERIALIZED_END_INDEXING_KEY
-                        + getBits(totalBytesWritten, /* numOfBits= */ 32);
+                SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
 
         String expectedIndexingBytesForAppCertificateIndexed =
-                SERIALIZED_START_INDEXING_KEY
-                        + getBits(totalBytesWritten, /* numOfBits= */ 32);
+                SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
         for (int count = 0; count < ruleCount; count++) {
             String appCertificate = String.format("%s%04d", appCertificatePrefix, count);
             if (count > 0 && count % INDEXING_BLOCK_SIZE == 0) {
@@ -579,33 +585,32 @@
             }
 
             byte[] bytesForPackage =
-                    getBytes(getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName(
-                            appCertificate));
+                    getBytes(
+                            getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName(
+                                    appCertificate));
             expectedOrderedRuleOutputStream.write(bytesForPackage);
             totalBytesWritten += bytesForPackage.length;
         }
         expectedIndexingBytesForAppCertificateIndexed +=
-                SERIALIZED_END_INDEXING_KEY
-                        + getBits(totalBytesWritten, /* numOfBits= */ 32);
+                SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
 
         String expectedIndexingBytesForUnindexed =
-                SERIALIZED_START_INDEXING_KEY
-                        + getBits(totalBytesWritten, /* numOfBits= */ 32);
+                SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
         for (int count = 0; count < ruleCount; count++) {
             byte[] bytesForPackage =
-                    getBytes(getSerializedCompoundRuleWithInstallerNameAndInstallerCert(
-                            String.format("%s%04d", installerNamePrefix, count)));
+                    getBytes(
+                            getSerializedCompoundRuleWithInstallerNameAndInstallerCert(
+                                    String.format("%s%04d", installerNamePrefix, count)));
             expectedOrderedRuleOutputStream.write(bytesForPackage);
             totalBytesWritten += bytesForPackage.length;
         }
         expectedIndexingBytesForUnindexed +=
-                SERIALIZED_END_INDEXING_KEY
-                        + getBits(totalBytesWritten, /* numOfBits= */ 32);
+                SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
         expectedIndexingOutputStream.write(
-                getBytes(expectedIndexingBytesForPackageNameIndexed
-                        + expectedIndexingBytesForAppCertificateIndexed
-                        + expectedIndexingBytesForUnindexed
-                        + getBits(1, 1)));
+                getBytes(
+                        expectedIndexingBytesForPackageNameIndexed
+                                + expectedIndexingBytesForAppCertificateIndexed
+                                + expectedIndexingBytesForUnindexed));
 
         assertThat(ruleOutputStream.toByteArray())
                 .isEqualTo(expectedOrderedRuleOutputStream.toByteArray());
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index e1c489e..355cada 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -112,7 +112,7 @@
 import android.net.NetworkStats;
 import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
-import android.net.StringNetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.INetworkManagementService;
@@ -1804,9 +1804,11 @@
                 .getService(NetworkPolicyManagerInternal.class);
         npmi.onStatsProviderLimitReached("TEST");
 
-        // Verifies that the limit reached leads to a force update.
+        // Verifies that the limit reached leads to a force update and new limit should be set.
         postMsgAndWaitForCompletion();
         verify(mStatsService).forceUpdate();
+        postMsgAndWaitForCompletion();
+        verify(mStatsService).setStatsProviderLimit(TEST_IFACE, 10000L - 4999L - 1999L);
     }
 
     /**
@@ -1911,7 +1913,8 @@
         if (!roaming) {
             nc.addCapability(NET_CAPABILITY_NOT_ROAMING);
         }
-        nc.setNetworkSpecifier(new StringNetworkSpecifier(String.valueOf(subId)));
+        nc.setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
+                .setSubscriptionId(subId).build());
         return nc;
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
index cc170af..c56034a 100644
--- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
@@ -335,7 +335,9 @@
             }).when(driver).getProperties_2_3(any());
         }
 
-        mService = new SoundTriggerMiddlewareImpl(mHalDriver, mAudioSessionProvider);
+        mService = new SoundTriggerMiddlewareImpl(() -> {
+            return mHalDriver;
+        }, mAudioSessionProvider);
     }
 
     private Pair<Integer, SoundTriggerHwCallback> loadGenericModel_2_0(ISoundTriggerModule module,
@@ -798,12 +800,12 @@
 
             @Override
             public boolean linkToDeath(DeathRecipient recipient, long cookie) {
-                throw new UnsupportedOperationException();
+                return true;
             }
 
             @Override
             public boolean unlinkToDeath(DeathRecipient recipient) {
-                throw new UnsupportedOperationException();
+                return true;
             }
         };
 
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
index 8a3183f..d940a6a 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
@@ -651,7 +651,6 @@
 
         @Override
         public long systemClockMillis() {
-            assertWakeLockAcquired();
             return mSystemClockMillis;
         }
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
index bd7d9ec..f5af3ec 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
@@ -64,7 +64,8 @@
 
         NotificationChannel updatedChannel =
                 new NotificationChannel("a", "", IMPORTANCE_HIGH);
-        when(mConfig.getNotificationChannel(any(), anyInt(), eq("a"), eq(null), eq(false)))
+        when(mConfig.getConversationNotificationChannel(
+                any(), anyInt(), eq("a"), eq(null), eq(true), eq(false)))
                 .thenReturn(updatedChannel);
 
         assertNull(extractor.process(r));
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 62f5230..2ac4642 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -471,16 +471,17 @@
         mTestableLooper.processAllMessages();
     }
 
-    private void setUpPrefsForBubbles(boolean globalEnabled, boolean pkgEnabled,
-            boolean channelEnabled) {
-        mService.setPreferencesHelper(mPreferencesHelper);
-        when(mPreferencesHelper.bubblesEnabled()).thenReturn(globalEnabled);
-        when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(pkgEnabled);
-        when(mPreferencesHelper.getNotificationChannel(
-                anyString(), anyInt(), anyString(), eq(null), anyBoolean())).thenReturn(
-                mTestNotificationChannel);
-        when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn(
-                mTestNotificationChannel.getImportance());
+    private void setUpPrefsForBubbles(String pkg, int uid, boolean globalEnabled,
+            boolean pkgEnabled, boolean channelEnabled) {
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.NOTIFICATION_BUBBLES, globalEnabled ? 1 : 0);
+        mService.mPreferencesHelper.updateBubblesEnabled();
+        assertEquals(globalEnabled, mService.mPreferencesHelper.bubblesEnabled());
+        try {
+            mBinderService.setBubblesAllowed(pkg, uid, pkgEnabled);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
         mTestNotificationChannel.setAllowBubbles(channelEnabled);
     }
 
@@ -1763,8 +1764,8 @@
         Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
         mBinderService.enqueueNotificationWithTag(PKG, PKG, "testTvExtenderChannelOverride_onTv", 0,
                 generateNotificationRecord(null, tv).getNotification(), 0);
-        verify(mPreferencesHelper, times(1)).getNotificationChannel(
-                anyString(), anyInt(), eq("foo"), eq(null), anyBoolean());
+        verify(mPreferencesHelper, times(1)).getConversationNotificationChannel(
+                anyString(), anyInt(), eq("foo"), eq(null), anyBoolean(), anyBoolean());
     }
 
     @Test
@@ -1778,9 +1779,9 @@
         Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
         mBinderService.enqueueNotificationWithTag(PKG, PKG, "testTvExtenderChannelOverride_notOnTv",
                 0, generateNotificationRecord(null, tv).getNotification(), 0);
-        verify(mPreferencesHelper, times(1)).getNotificationChannel(
+        verify(mPreferencesHelper, times(1)).getConversationNotificationChannel(
                 anyString(), anyInt(), eq(mTestNotificationChannel.getId()), eq(null),
-                anyBoolean());
+                anyBoolean(), anyBoolean());
     }
 
     @Test
@@ -4759,7 +4760,7 @@
     @Test
     public void testFlagBubble() throws RemoteException {
         // Bubbles are allowed!
-        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
 
         NotificationRecord nr =
                 generateMessageBubbleNotifRecord(mTestNotificationChannel, "testFlagBubble");
@@ -4778,7 +4779,7 @@
     @Test
     public void testFlagBubble_noFlag_appNotAllowed() throws RemoteException {
         // Bubbles are allowed!
-        setUpPrefsForBubbles(true /* global */, false /* app */, true /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, false /* app */, true /* channel */);
 
         NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
                         "testFlagBubble_noFlag_appNotAllowed");
@@ -4797,7 +4798,7 @@
     @Test
     public void testFlagBubbleNotifs_noFlag_whenAppForeground() throws RemoteException {
         // Bubbles are allowed!
-        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
 
         // Notif with bubble metadata but not our other misc requirements
         Notification.Builder nb = new Notification.Builder(mContext,
@@ -4825,7 +4826,7 @@
     @Test
     public void testFlagBubbleNotifs_flag_messaging() throws RemoteException {
         // Bubbles are allowed!
-        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
 
         NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
                 "testFlagBubbleNotifs_flag_messaging");
@@ -4842,7 +4843,7 @@
     @Test
     public void testFlagBubbleNotifs_flag_phonecall() throws RemoteException {
         // Bubbles are allowed!
-        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
 
         // Give it bubble metadata
         Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
@@ -4878,7 +4879,7 @@
     @Test
     public void testFlagBubbleNotifs_noFlag_phonecall_noForegroundService() throws RemoteException {
         // Bubbles are allowed!
-        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
 
         // Give it bubble metadata
         Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
@@ -4911,7 +4912,7 @@
     @Test
     public void testFlagBubbleNotifs_noFlag_phonecall_noPerson() throws RemoteException {
         // Bubbles are allowed!
-        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
 
         // Give it bubble metadata
         Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
@@ -4942,7 +4943,7 @@
     @Test
     public void testFlagBubbleNotifs_noFlag_phonecall_noCategory() throws RemoteException {
         // Bubbles are allowed!
-        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
 
         // Give it bubble metadata
         Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
@@ -4977,7 +4978,7 @@
     @Test
     public void testFlagBubbleNotifs_noFlag_messaging_appNotAllowed() throws RemoteException {
         // Bubbles are NOT allowed!
-        setUpPrefsForBubbles(false /* global */, true /* app */, true /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, false /* app */, true /* channel */);
 
         NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
                 "testFlagBubbleNotifs_noFlag_messaging_appNotAllowed");
@@ -4995,7 +4996,7 @@
     @Test
     public void testFlagBubbleNotifs_noFlag_notBubble() throws RemoteException {
         // Bubbles are allowed!
-        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
 
         // Messaging notif WITHOUT bubble metadata
         Notification.Builder nb = getMessageStyleNotifBuilder(false /* addBubbleMetadata */,
@@ -5019,7 +5020,7 @@
     @Test
     public void testFlagBubbleNotifs_noFlag_messaging_channelNotAllowed() throws RemoteException {
         // Bubbles are allowed except on this channel
-        setUpPrefsForBubbles(true /* global */, true /* app */, false /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, false /* channel */);
 
         NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
                 "testFlagBubbleNotifs_noFlag_messaging_channelNotAllowed");
@@ -5037,7 +5038,7 @@
     @Test
     public void testFlagBubbleNotifs_noFlag_phonecall_notAllowed() throws RemoteException {
         // Bubbles are not allowed!
-        setUpPrefsForBubbles(false /* global */, true /* app */, true /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, false /* global */, true /* app */, true /* channel */);
 
         // Give it bubble metadata
         Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
@@ -5072,7 +5073,7 @@
     @Test
     public void testFlagBubbleNotifs_noFlag_phonecall_channelNotAllowed() throws RemoteException {
         // Bubbles are allowed, but not on channel.
-        setUpPrefsForBubbles(true /* global */, true /* app */, false /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, false /* channel */);
 
         // Give it bubble metadata
         Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
@@ -5280,7 +5281,7 @@
     @Test
     public void testNotificationBubbleChanged_false() throws Exception {
         // Bubbles are allowed!
-        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
 
         // Notif with bubble metadata
         NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
@@ -5311,7 +5312,7 @@
     @Test
     public void testNotificationBubbleChanged_true() throws Exception {
         // Bubbles are allowed!
-        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
 
         // Notif that is not a bubble
         NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
@@ -5348,7 +5349,7 @@
     @Test
     public void testNotificationBubbleChanged_true_notAllowed() throws Exception {
         // Bubbles are allowed!
-        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
 
         // Notif that is not a bubble
         NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
@@ -5547,7 +5548,7 @@
     @Test
     public void testNotificationBubbles_disabled_lowRamDevice() throws Exception {
         // Bubbles are allowed!
-        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
 
         // And we are low ram
         when(mActivityManager.isLowRamDevice()).thenReturn(true);
@@ -5631,7 +5632,7 @@
     public void testNotificationBubbles_flagAutoExpandForeground_fails_notForeground()
             throws Exception {
         // Bubbles are allowed!
-        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
 
         NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
                 "testNotificationBubbles_flagAutoExpandForeground_fails_notForeground");
@@ -5661,7 +5662,7 @@
     public void testNotificationBubbles_flagAutoExpandForeground_succeeds_foreground()
             throws RemoteException {
         // Bubbles are allowed!
-        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
 
         NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
                 "testNotificationBubbles_flagAutoExpandForeground_succeeds_foreground");
@@ -5691,7 +5692,7 @@
     public void testNotificationBubbles_bubbleChildrenStay_whenGroupSummaryDismissed()
             throws Exception {
         // Bubbles are allowed!
-        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
 
         NotificationRecord nrSummary = addGroupWithBubblesAndValidateAdded(
                 true /* summaryAutoCancel */);
@@ -5714,7 +5715,7 @@
     public void testNotificationBubbles_bubbleChildrenStay_whenGroupSummaryClicked()
             throws Exception {
         // Bubbles are allowed!
-        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
 
         NotificationRecord nrSummary = addGroupWithBubblesAndValidateAdded(
                 true /* summaryAutoCancel */);
@@ -5836,7 +5837,7 @@
         mBinderService.createConversationNotificationChannelForPackage(PKG, mUid, orig, "friend");
 
         NotificationChannel friendChannel = mBinderService.getConversationNotificationChannel(
-                PKG, 0, PKG, original.getId(), "friend");
+                PKG, 0, PKG, original.getId(), false, "friend");
 
         assertEquals(original.getName(), friendChannel.getName());
         assertEquals(original.getId(), friendChannel.getParentChannelId());
@@ -5874,9 +5875,9 @@
                 conversationId);
 
         NotificationChannel messagesChild = mBinderService.getConversationNotificationChannel(
-                PKG, 0, PKG, messagesParent.getId(), conversationId);
+                PKG, 0, PKG, messagesParent.getId(), false, conversationId);
         NotificationChannel callsChild = mBinderService.getConversationNotificationChannel(
-                PKG, 0, PKG, callsParent.getId(), conversationId);
+                PKG, 0, PKG, callsParent.getId(), false, conversationId);
 
         assertEquals(messagesParent.getId(), messagesChild.getParentChannelId());
         assertEquals(conversationId, messagesChild.getConversationId());
@@ -5887,8 +5888,8 @@
         mBinderService.deleteConversationNotificationChannels(PKG, mUid, conversationId);
 
         assertNull(mBinderService.getConversationNotificationChannel(
-                PKG, 0, PKG, messagesParent.getId(), conversationId));
+                PKG, 0, PKG, messagesParent.getId(), false, conversationId));
         assertNull(mBinderService.getConversationNotificationChannel(
-                PKG, 0, PKG, callsParent.getId(), conversationId));
+                PKG, 0, PKG, callsParent.getId(), false, conversationId));
     }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 7173e0c..7ac45f0 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -354,6 +354,7 @@
         channel2.setGroup(ncg.getId());
         channel2.setVibrationPattern(new long[]{100, 67, 145, 156});
         channel2.setLightColor(Color.BLUE);
+        channel2.setConversationId("id1", "conversation");
 
         mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg, true);
         mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg2, true);
@@ -2822,8 +2823,20 @@
         friend.setConversationId(parent.getId(), conversationId);
         mHelper.createNotificationChannel(PKG_O, UID_O, friend, true, false);
 
-        compareChannelsParentChild(parent, mHelper.getNotificationChannel(
-                PKG_O, UID_O, parent.getId(), conversationId, false), conversationId);
+        compareChannelsParentChild(parent, mHelper.getConversationNotificationChannel(
+                PKG_O, UID_O, parent.getId(), conversationId, false, false), conversationId);
+    }
+
+    @Test
+    public void testGetNotificationChannel_conversationProvidedByNotCustomizedYet() {
+        String conversationId = "friend";
+
+        NotificationChannel parent =
+                new NotificationChannel("parent", "messages", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG_O, UID_O, parent, true, false);
+
+        compareChannels(parent, mHelper.getConversationNotificationChannel(
+                PKG_O, UID_O, parent.getId(), conversationId, true, false));
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
index 399cf49..135d005 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
 import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
@@ -44,6 +45,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.app.BlockedAppActivity;
 import com.android.internal.app.HarmfulAppWarningActivity;
 import com.android.internal.app.SuspendedAppActivity;
 import com.android.internal.app.UnlaunchableAppActivity;
@@ -105,6 +107,8 @@
     private PackageManagerService mPackageManager;
     @Mock
     private ActivityManagerInternal mAmInternal;
+    @Mock
+    private LockTaskController mLockTaskController;
 
     private ActivityStartInterceptor mInterceptor;
     private ActivityInfo mAInfo = new ActivityInfo();
@@ -145,6 +149,13 @@
         when(mPackageManager.getHarmfulAppWarning(TEST_PACKAGE_NAME, TEST_USER_ID))
                 .thenReturn(null);
 
+        // Mock LockTaskController
+        mAInfo.lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_DEFAULT;
+        when(mService.getLockTaskController()).thenReturn(mLockTaskController);
+        when(mLockTaskController.isActivityAllowed(
+                TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT))
+                .thenReturn(true);
+
         // Initialise activity info
         mAInfo.applicationInfo = new ApplicationInfo();
         mAInfo.packageName = mAInfo.applicationInfo.packageName = TEST_PACKAGE_NAME;
@@ -196,6 +207,18 @@
     }
 
     @Test
+    public void testInterceptLockTaskModeViolationPackage() {
+        when(mLockTaskController.isActivityAllowed(
+                TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT))
+                .thenReturn(false);
+
+        assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null));
+
+        assertTrue(BlockedAppActivity.createIntent(TEST_USER_ID, TEST_PACKAGE_NAME)
+                .filterEquals(mInterceptor.mIntent));
+    }
+
+    @Test
     public void testInterceptQuietProfile() {
         // GIVEN that the user the activity is starting as is currently in quiet mode
         when(mUserManager.isQuietModeEnabled(eq(UserHandle.of(TEST_USER_ID)))).thenReturn(true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index 079c49f..d22502d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -27,6 +27,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
@@ -153,6 +154,19 @@
     }
 
     @Test
+    public void testContainerChanges() {
+        removeGlobalMinSizeRestriction();
+        final ActivityStack stack = new StackBuilder(mRootWindowContainer)
+                .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+        final Task task = stack.getTopMostTask();
+        WindowContainerTransaction t = new WindowContainerTransaction();
+        assertTrue(task.isFocusable());
+        t.setFocusable(stack.mRemoteToken, false);
+        mService.applyContainerTransaction(t);
+        assertFalse(task.isFocusable());
+    }
+
+    @Test
     public void testDisplayWindowListener() {
         final ArrayList<Integer> added = new ArrayList<>();
         final ArrayList<Integer> changed = new ArrayList<>();
diff --git a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
index 039ff60..75ec53d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
@@ -29,6 +29,9 @@
 import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD;
 import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NONE;
 import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
 import static android.os.Process.SYSTEM_UID;
 import static android.telecom.TelecomManager.EMERGENCY_DIALER_COMPONENT;
 
@@ -693,6 +696,38 @@
         assertTrue((StatusBarManager.DISABLE2_QUICK_SETTINGS & flags.second) != 0);
     }
 
+    @Test
+    public void testIsActivityAllowed() {
+        // WHEN lock task mode is not enabled
+        assertTrue(mLockTaskController.isActivityAllowed(
+                TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT));
+
+        // WHEN lock task mode is enabled
+        Task tr = getTask(Task.LOCK_TASK_AUTH_WHITELISTED);
+        mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
+
+
+        // package with LOCK_TASK_LAUNCH_MODE_ALWAYS should always be allowed
+        assertTrue(mLockTaskController.isActivityAllowed(
+                TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_ALWAYS));
+
+        // unwhitelisted package should not be allowed
+        assertFalse(mLockTaskController.isActivityAllowed(
+                TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT));
+
+        // update the whitelist
+        String[] whitelist = new String[] { TEST_PACKAGE_NAME };
+        mLockTaskController.updateLockTaskPackages(TEST_USER_ID, whitelist);
+
+        // whitelisted package should be allowed
+        assertTrue(mLockTaskController.isActivityAllowed(
+                TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT));
+
+        // package with LOCK_TASK_LAUNCH_MODE_NEVER should never be allowed
+        assertFalse(mLockTaskController.isActivityAllowed(
+                TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_NEVER));
+    }
+
     private Task getTask(int lockTaskAuth) {
         return getTask(TEST_PACKAGE_NAME, lockTaskAuth);
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
index 04d79ca..ea8d082 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
@@ -253,12 +253,12 @@
 
         // Under split screen primary we should be focusable when not minimized
         mRootWindowContainer.setDockedStackMinimized(false);
-        assertTrue(stack.isFocusable());
+        assertTrue(stack.isTopActivityFocusable());
         assertTrue(activity.isFocusable());
 
         // Under split screen primary we should not be focusable when minimized
         mRootWindowContainer.setDockedStackMinimized(true);
-        assertFalse(stack.isFocusable());
+        assertFalse(stack.isTopActivityFocusable());
         assertFalse(activity.isFocusable());
 
         final ActivityStack pinnedStack = mRootWindowContainer.getDefaultDisplay().createStack(
@@ -267,19 +267,19 @@
                 .setStack(pinnedStack).build();
 
         // We should not be focusable when in pinned mode
-        assertFalse(pinnedStack.isFocusable());
+        assertFalse(pinnedStack.isTopActivityFocusable());
         assertFalse(pinnedActivity.isFocusable());
 
         // Add flag forcing focusability.
         pinnedActivity.info.flags |= FLAG_ALWAYS_FOCUSABLE;
 
         // We should not be focusable when in pinned mode
-        assertTrue(pinnedStack.isFocusable());
+        assertTrue(pinnedStack.isTopActivityFocusable());
         assertTrue(pinnedActivity.isFocusable());
 
         // Without the overridding activity, stack should not be focusable.
         pinnedStack.removeChild(pinnedActivity.getTask(), "testFocusability");
-        assertFalse(pinnedStack.isFocusable());
+        assertFalse(pinnedStack.isTopActivityFocusable());
     }
 
     /**
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 8397aa4..b8cd378 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -2069,6 +2069,13 @@
         }
 
         @Override
+        public UsageEvents queryEventsForUser(int userId, long beginTime, long endTime,
+                boolean shouldObfuscateInstantApps) {
+            return UsageStatsService.this.queryEvents(
+                    userId, beginTime, endTime, shouldObfuscateInstantApps);
+        }
+
+        @Override
         public void setLastJobRunTime(String packageName, int userId, long elapsedRealtime) {
             mAppStandby.setLastJobRunTime(packageName, userId, elapsedRealtime);
         }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index bde2cfd..7099c09 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -756,7 +756,9 @@
             return;
         }
 
-        model.setStopped();
+        if (event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) {
+            model.setStopped();
+        }
 
         try {
             callback.onGenericSoundTriggerDetected((GenericRecognitionEvent) event);
@@ -900,7 +902,9 @@
             return;
         }
 
-        modelData.setStopped();
+        if (event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) {
+            modelData.setStopped();
+        }
 
         try {
             modelData.getCallback().onKeyphraseDetected((KeyphraseRecognitionEvent) event);
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index a8852a8..826a89e 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -568,6 +568,7 @@
         private final Bundle mExtras;
         private final Bundle mIntentExtras;
         private final long mCreationTimeMillis;
+        private final String mContactDisplayName;
         private final @CallDirection int mCallDirection;
         private final @Connection.VerificationStatus int mCallerNumberVerificationStatus;
 
@@ -872,6 +873,17 @@
         }
 
         /**
+         * Returns the name of the caller on the remote end, as derived from a
+         * {@link android.provider.ContactsContract} lookup of the call's handle.
+         * @return The name of the caller, or {@code null} if the lookup is not yet complete, if
+         *         there's no contacts entry for the caller, or if the {@link InCallService} does
+         *         not hold the {@link android.Manifest.permission#READ_CONTACTS} permission.
+         */
+        public @Nullable String getContactDisplayName() {
+            return mContactDisplayName;
+        }
+
+        /**
          * Indicates whether the call is an incoming or outgoing call.
          * @return The call's direction.
          */
@@ -909,6 +921,7 @@
                         areBundlesEqual(mExtras, d.mExtras) &&
                         areBundlesEqual(mIntentExtras, d.mIntentExtras) &&
                         Objects.equals(mCreationTimeMillis, d.mCreationTimeMillis) &&
+                        Objects.equals(mContactDisplayName, d.mContactDisplayName) &&
                         Objects.equals(mCallDirection, d.mCallDirection) &&
                         Objects.equals(mCallerNumberVerificationStatus,
                                 d.mCallerNumberVerificationStatus);
@@ -933,6 +946,7 @@
                             mExtras,
                             mIntentExtras,
                             mCreationTimeMillis,
+                            mContactDisplayName,
                             mCallDirection,
                             mCallerNumberVerificationStatus);
         }
@@ -955,6 +969,7 @@
                 Bundle extras,
                 Bundle intentExtras,
                 long creationTimeMillis,
+                String contactDisplayName,
                 int callDirection,
                 int callerNumberVerificationStatus) {
             mTelecomCallId = telecomCallId;
@@ -973,6 +988,7 @@
             mExtras = extras;
             mIntentExtras = intentExtras;
             mCreationTimeMillis = creationTimeMillis;
+            mContactDisplayName = contactDisplayName;
             mCallDirection = callDirection;
             mCallerNumberVerificationStatus = callerNumberVerificationStatus;
         }
@@ -996,6 +1012,7 @@
                     parcelableCall.getExtras(),
                     parcelableCall.getIntentExtras(),
                     parcelableCall.getCreationTimeMillis(),
+                    parcelableCall.getContactDisplayName(),
                     parcelableCall.getCallDirection(),
                     parcelableCall.getCallerNumberVerificationStatus());
         }
@@ -1445,6 +1462,7 @@
 
     private boolean mChildrenCached;
     private String mParentId = null;
+    private String mActiveGenericConferenceChild = null;
     private int mState;
     private List<String> mCannedTextResponses = null;
     private String mCallingPackage;
@@ -1943,6 +1961,20 @@
     }
 
     /**
+     * Returns the child {@link Call} in a generic conference that is currently active.
+     * For calls that are not generic conferences, or when the generic conference has more than
+     * 2 children, returns {@code null}.
+     * @see Details#PROPERTY_GENERIC_CONFERENCE
+     * @return The active child call.
+     */
+    public @Nullable Call getGenericConferenceActiveChildCall() {
+        if (mActiveGenericConferenceChild != null) {
+            return mPhone.internalGetCallByTelecomId(mActiveGenericConferenceChild);
+        }
+        return null;
+    }
+
+    /**
      * Obtains a list of canned, pre-configured message responses to present to the user as
      * ways of rejecting this {@code Call} using via a text message.
      *
@@ -2190,6 +2222,13 @@
             mChildrenCached = false;
         }
 
+        String activeChildCallId = parcelableCall.getActiveChildCallId();
+        boolean activeChildChanged = !Objects.equals(activeChildCallId,
+                mActiveGenericConferenceChild);
+        if (activeChildChanged) {
+            mActiveGenericConferenceChild = activeChildCallId;
+        }
+
         List<String> conferenceableCallIds = parcelableCall.getConferenceableCallIds();
         List<Call> conferenceableCalls = new ArrayList<Call>(conferenceableCallIds.size());
         for (String otherId : conferenceableCallIds) {
@@ -2249,7 +2288,7 @@
         if (parentChanged) {
             fireParentChanged(getParent());
         }
-        if (childrenChanged) {
+        if (childrenChanged || activeChildChanged) {
             fireChildrenChanged(getChildren());
         }
         if (isRttChanged) {
diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java
index be4e2f4..415a817 100644
--- a/telecomm/java/android/telecom/ParcelableCall.java
+++ b/telecomm/java/android/telecom/ParcelableCall.java
@@ -16,6 +16,7 @@
 
 package android.telecom;
 
+import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.net.Uri;
 import android.os.Build;
@@ -36,6 +37,265 @@
  * @hide
  */
 public final class ParcelableCall implements Parcelable {
+
+    public static class ParcelableCallBuilder {
+        private String mId;
+        private int mState;
+        private DisconnectCause mDisconnectCause;
+        private List<String> mCannedSmsResponses;
+        private int mCapabilities;
+        private int mProperties;
+        private int mSupportedAudioRoutes;
+        private long mConnectTimeMillis;
+        private Uri mHandle;
+        private int mHandlePresentation;
+        private String mCallerDisplayName;
+        private int mCallerDisplayNamePresentation;
+        private GatewayInfo mGatewayInfo;
+        private PhoneAccountHandle mAccountHandle;
+        private boolean mIsVideoCallProviderChanged;
+        private IVideoProvider mVideoCallProvider;
+        private boolean mIsRttCallChanged;
+        private ParcelableRttCall mRttCall;
+        private String mParentCallId;
+        private List<String> mChildCallIds;
+        private StatusHints mStatusHints;
+        private int mVideoState;
+        private List<String> mConferenceableCallIds;
+        private Bundle mIntentExtras;
+        private Bundle mExtras;
+        private long mCreationTimeMillis;
+        private int mCallDirection;
+        private int mCallerNumberVerificationStatus;
+        private String mContactDisplayName;
+        private String mActiveChildCallId;
+
+        public ParcelableCallBuilder setId(String id) {
+            mId = id;
+            return this;
+        }
+
+        public ParcelableCallBuilder setState(int state) {
+            mState = state;
+            return this;
+        }
+
+        public ParcelableCallBuilder setDisconnectCause(DisconnectCause disconnectCause) {
+            mDisconnectCause = disconnectCause;
+            return this;
+        }
+
+        public ParcelableCallBuilder setCannedSmsResponses(List<String> cannedSmsResponses) {
+            mCannedSmsResponses = cannedSmsResponses;
+            return this;
+        }
+
+        public ParcelableCallBuilder setCapabilities(int capabilities) {
+            mCapabilities = capabilities;
+            return this;
+        }
+
+        public ParcelableCallBuilder setProperties(int properties) {
+            mProperties = properties;
+            return this;
+        }
+
+        public ParcelableCallBuilder setSupportedAudioRoutes(int supportedAudioRoutes) {
+            mSupportedAudioRoutes = supportedAudioRoutes;
+            return this;
+        }
+
+        public ParcelableCallBuilder setConnectTimeMillis(long connectTimeMillis) {
+            mConnectTimeMillis = connectTimeMillis;
+            return this;
+        }
+
+        public ParcelableCallBuilder setHandle(Uri handle) {
+            mHandle = handle;
+            return this;
+        }
+
+        public ParcelableCallBuilder setHandlePresentation(int handlePresentation) {
+            mHandlePresentation = handlePresentation;
+            return this;
+        }
+
+        public ParcelableCallBuilder setCallerDisplayName(String callerDisplayName) {
+            mCallerDisplayName = callerDisplayName;
+            return this;
+        }
+
+        public ParcelableCallBuilder setCallerDisplayNamePresentation(
+                int callerDisplayNamePresentation) {
+            mCallerDisplayNamePresentation = callerDisplayNamePresentation;
+            return this;
+        }
+
+        public ParcelableCallBuilder setGatewayInfo(GatewayInfo gatewayInfo) {
+            mGatewayInfo = gatewayInfo;
+            return this;
+        }
+
+        public ParcelableCallBuilder setAccountHandle(PhoneAccountHandle accountHandle) {
+            mAccountHandle = accountHandle;
+            return this;
+        }
+
+        public ParcelableCallBuilder setIsVideoCallProviderChanged(
+                boolean isVideoCallProviderChanged) {
+            mIsVideoCallProviderChanged = isVideoCallProviderChanged;
+            return this;
+        }
+
+        public ParcelableCallBuilder setVideoCallProvider(IVideoProvider videoCallProvider) {
+            mVideoCallProvider = videoCallProvider;
+            return this;
+        }
+
+        public ParcelableCallBuilder setIsRttCallChanged(boolean isRttCallChanged) {
+            mIsRttCallChanged = isRttCallChanged;
+            return this;
+        }
+
+        public ParcelableCallBuilder setRttCall(ParcelableRttCall rttCall) {
+            mRttCall = rttCall;
+            return this;
+        }
+
+        public ParcelableCallBuilder setParentCallId(String parentCallId) {
+            mParentCallId = parentCallId;
+            return this;
+        }
+
+        public ParcelableCallBuilder setChildCallIds(List<String> childCallIds) {
+            mChildCallIds = childCallIds;
+            return this;
+        }
+
+        public ParcelableCallBuilder setStatusHints(StatusHints statusHints) {
+            mStatusHints = statusHints;
+            return this;
+        }
+
+        public ParcelableCallBuilder setVideoState(int videoState) {
+            mVideoState = videoState;
+            return this;
+        }
+
+        public ParcelableCallBuilder setConferenceableCallIds(
+                List<String> conferenceableCallIds) {
+            mConferenceableCallIds = conferenceableCallIds;
+            return this;
+        }
+
+        public ParcelableCallBuilder setIntentExtras(Bundle intentExtras) {
+            mIntentExtras = intentExtras;
+            return this;
+        }
+
+        public ParcelableCallBuilder setExtras(Bundle extras) {
+            mExtras = extras;
+            return this;
+        }
+
+        public ParcelableCallBuilder setCreationTimeMillis(long creationTimeMillis) {
+            mCreationTimeMillis = creationTimeMillis;
+            return this;
+        }
+
+        public ParcelableCallBuilder setCallDirection(int callDirection) {
+            mCallDirection = callDirection;
+            return this;
+        }
+
+        public ParcelableCallBuilder setCallerNumberVerificationStatus(
+                int callerNumberVerificationStatus) {
+            mCallerNumberVerificationStatus = callerNumberVerificationStatus;
+            return this;
+        }
+
+        public ParcelableCallBuilder setContactDisplayName(String contactDisplayName) {
+            mContactDisplayName = contactDisplayName;
+            return this;
+        }
+
+        public ParcelableCallBuilder setActiveChildCallId(String activeChildCallId) {
+            mActiveChildCallId = activeChildCallId;
+            return this;
+        }
+
+        public ParcelableCall createParcelableCall() {
+            return new ParcelableCall(
+                    mId,
+                    mState,
+                    mDisconnectCause,
+                    mCannedSmsResponses,
+                    mCapabilities,
+                    mProperties,
+                    mSupportedAudioRoutes,
+                    mConnectTimeMillis,
+                    mHandle,
+                    mHandlePresentation,
+                    mCallerDisplayName,
+                    mCallerDisplayNamePresentation,
+                    mGatewayInfo,
+                    mAccountHandle,
+                    mIsVideoCallProviderChanged,
+                    mVideoCallProvider,
+                    mIsRttCallChanged,
+                    mRttCall,
+                    mParentCallId,
+                    mChildCallIds,
+                    mStatusHints,
+                    mVideoState,
+                    mConferenceableCallIds,
+                    mIntentExtras,
+                    mExtras,
+                    mCreationTimeMillis,
+                    mCallDirection,
+                    mCallerNumberVerificationStatus,
+                    mContactDisplayName,
+                    mActiveChildCallId);
+        }
+
+        public static ParcelableCallBuilder fromParcelableCall(ParcelableCall parcelableCall) {
+            ParcelableCallBuilder newBuilder = new ParcelableCallBuilder();
+            newBuilder.mId = parcelableCall.mId;
+            newBuilder.mState = parcelableCall.mState;
+            newBuilder.mDisconnectCause = parcelableCall.mDisconnectCause;
+            newBuilder.mCannedSmsResponses = parcelableCall.mCannedSmsResponses;
+            newBuilder.mCapabilities = parcelableCall.mCapabilities;
+            newBuilder.mProperties = parcelableCall.mProperties;
+            newBuilder.mSupportedAudioRoutes = parcelableCall.mSupportedAudioRoutes;
+            newBuilder.mConnectTimeMillis = parcelableCall.mConnectTimeMillis;
+            newBuilder.mHandle = parcelableCall.mHandle;
+            newBuilder.mHandlePresentation = parcelableCall.mHandlePresentation;
+            newBuilder.mCallerDisplayName = parcelableCall.mCallerDisplayName;
+            newBuilder.mCallerDisplayNamePresentation =
+                    parcelableCall.mCallerDisplayNamePresentation;
+            newBuilder.mGatewayInfo = parcelableCall.mGatewayInfo;
+            newBuilder.mAccountHandle = parcelableCall.mAccountHandle;
+            newBuilder.mIsVideoCallProviderChanged = parcelableCall.mIsVideoCallProviderChanged;
+            newBuilder.mVideoCallProvider = parcelableCall.mVideoCallProvider;
+            newBuilder.mIsRttCallChanged = parcelableCall.mIsRttCallChanged;
+            newBuilder.mRttCall = parcelableCall.mRttCall;
+            newBuilder.mParentCallId = parcelableCall.mParentCallId;
+            newBuilder.mChildCallIds = parcelableCall.mChildCallIds;
+            newBuilder.mStatusHints = parcelableCall.mStatusHints;
+            newBuilder.mVideoState = parcelableCall.mVideoState;
+            newBuilder.mConferenceableCallIds = parcelableCall.mConferenceableCallIds;
+            newBuilder.mIntentExtras = parcelableCall.mIntentExtras;
+            newBuilder.mExtras = parcelableCall.mExtras;
+            newBuilder.mCreationTimeMillis = parcelableCall.mCreationTimeMillis;
+            newBuilder.mCallDirection = parcelableCall.mCallDirection;
+            newBuilder.mCallerNumberVerificationStatus =
+                    parcelableCall.mCallerNumberVerificationStatus;
+            newBuilder.mContactDisplayName = parcelableCall.mContactDisplayName;
+            newBuilder.mActiveChildCallId = parcelableCall.mActiveChildCallId;
+            return newBuilder;
+        }
+    }
+
     private final String mId;
     private final int mState;
     private final DisconnectCause mDisconnectCause;
@@ -65,6 +325,8 @@
     private final long mCreationTimeMillis;
     private final int mCallDirection;
     private final int mCallerNumberVerificationStatus;
+    private final String mContactDisplayName;
+    private final String mActiveChildCallId; // Only valid for CDMA conferences
 
     public ParcelableCall(
             String id,
@@ -94,7 +356,10 @@
             Bundle extras,
             long creationTimeMillis,
             int callDirection,
-            int callerNumberVerificationStatus) {
+            int callerNumberVerificationStatus,
+            String contactDisplayName,
+            String activeChildCallId
+    ) {
         mId = id;
         mState = state;
         mDisconnectCause = disconnectCause;
@@ -123,6 +388,8 @@
         mCreationTimeMillis = creationTimeMillis;
         mCallDirection = callDirection;
         mCallerNumberVerificationStatus = callerNumberVerificationStatus;
+        mContactDisplayName = contactDisplayName;
+        mActiveChildCallId = activeChildCallId;
     }
 
     /** The unique ID of the call. */
@@ -332,6 +599,21 @@
         return mCallerNumberVerificationStatus;
     }
 
+    /**
+     * @return the name of the remote party as derived from a contacts DB lookup.
+     */
+    public @Nullable String getContactDisplayName() {
+        return mContactDisplayName;
+    }
+
+    /**
+     * @return On a CDMA conference with two participants, returns the ID of the child call that's
+     *         currently active.
+     */
+    public @Nullable String getActiveChildCallId() {
+        return mActiveChildCallId;
+    }
+
     /** Responsible for creating ParcelableCall objects for deserialized Parcels. */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     public static final @android.annotation.NonNull Parcelable.Creator<ParcelableCall> CREATOR =
@@ -371,35 +653,40 @@
             long creationTimeMillis = source.readLong();
             int callDirection = source.readInt();
             int callerNumberVerificationStatus = source.readInt();
-            return new ParcelableCall(
-                    id,
-                    state,
-                    disconnectCause,
-                    cannedSmsResponses,
-                    capabilities,
-                    properties,
-                    supportedAudioRoutes,
-                    connectTimeMillis,
-                    handle,
-                    handlePresentation,
-                    callerDisplayName,
-                    callerDisplayNamePresentation,
-                    gatewayInfo,
-                    accountHandle,
-                    isVideoCallProviderChanged,
-                    videoCallProvider,
-                    isRttCallChanged,
-                    rttCall,
-                    parentCallId,
-                    childCallIds,
-                    statusHints,
-                    videoState,
-                    conferenceableCallIds,
-                    intentExtras,
-                    extras,
-                    creationTimeMillis,
-                    callDirection,
-                    callerNumberVerificationStatus);
+            String contactDisplayName = source.readString();
+            String activeChildCallId = source.readString();
+            return new ParcelableCallBuilder()
+                    .setId(id)
+                    .setState(state)
+                    .setDisconnectCause(disconnectCause)
+                    .setCannedSmsResponses(cannedSmsResponses)
+                    .setCapabilities(capabilities)
+                    .setProperties(properties)
+                    .setSupportedAudioRoutes(supportedAudioRoutes)
+                    .setConnectTimeMillis(connectTimeMillis)
+                    .setHandle(handle)
+                    .setHandlePresentation(handlePresentation)
+                    .setCallerDisplayName(callerDisplayName)
+                    .setCallerDisplayNamePresentation(callerDisplayNamePresentation)
+                    .setGatewayInfo(gatewayInfo)
+                    .setAccountHandle(accountHandle)
+                    .setIsVideoCallProviderChanged(isVideoCallProviderChanged)
+                    .setVideoCallProvider(videoCallProvider)
+                    .setIsRttCallChanged(isRttCallChanged)
+                    .setRttCall(rttCall)
+                    .setParentCallId(parentCallId)
+                    .setChildCallIds(childCallIds)
+                    .setStatusHints(statusHints)
+                    .setVideoState(videoState)
+                    .setConferenceableCallIds(conferenceableCallIds)
+                    .setIntentExtras(intentExtras)
+                    .setExtras(extras)
+                    .setCreationTimeMillis(creationTimeMillis)
+                    .setCallDirection(callDirection)
+                    .setCallerNumberVerificationStatus(callerNumberVerificationStatus)
+                    .setContactDisplayName(contactDisplayName)
+                    .setActiveChildCallId(activeChildCallId)
+                    .createParcelableCall();
         }
 
         @Override
@@ -446,6 +733,8 @@
         destination.writeLong(mCreationTimeMillis);
         destination.writeInt(mCallDirection);
         destination.writeInt(mCallerNumberVerificationStatus);
+        destination.writeString(mContactDisplayName);
+        destination.writeString(mActiveChildCallId);
     }
 
     @Override
diff --git a/telephony/java/android/telephony/PreciseDataConnectionState.java b/telephony/java/android/telephony/PreciseDataConnectionState.java
index 5e2e554..0cfb8c5 100644
--- a/telephony/java/android/telephony/PreciseDataConnectionState.java
+++ b/telephony/java/android/telephony/PreciseDataConnectionState.java
@@ -138,7 +138,7 @@
      * To check the SDK version for {@link PreciseDataConnectionState#getDataConnectionState}.
      */
     @ChangeId
-    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
     private static final long GET_DATA_CONNECTION_STATE_CODE_CHANGE = 147600208L;
 
     /**
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 2c62d06..ad8ac76 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -1624,7 +1624,8 @@
      * @return Current data network type
      * @hide
      */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    @SystemApi
+    @TestApi
     public @NetworkType int getDataNetworkType() {
         final NetworkRegistrationInfo iwlanRegInfo = getNetworkRegistrationInfo(
                 NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 0ffeccf..83c241a 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -1104,6 +1104,16 @@
      */
     public static final int CDMA_ROAMING_MODE_ANY = 2;
 
+    /** @hide */
+    @IntDef(prefix = { "CDMA_ROAMING_MODE_" }, value = {
+            CDMA_ROAMING_MODE_RADIO_DEFAULT,
+            CDMA_ROAMING_MODE_HOME,
+            CDMA_ROAMING_MODE_AFFILIATED,
+            CDMA_ROAMING_MODE_ANY
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface CdmaRoamingMode{}
+
     /**
      * An unknown carrier id. It could either be subscription unavailable or the subscription
      * carrier cannot be recognized. Unrecognized carriers here means
@@ -5439,7 +5449,7 @@
      * To check the SDK version for {@link TelephonyManager#getDataState}.
      */
     @ChangeId
-    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
     private static final long GET_DATA_STATE_CODE_CHANGE = 147600208L;
 
     /**
@@ -5525,7 +5535,7 @@
      * To check the SDK version for {@link TelephonyManager#listen}.
      */
     @ChangeId
-    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.P)
     private static final long LISTEN_CODE_CHANGE = 147600208L;
 
     /**
@@ -8974,8 +8984,9 @@
      *
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
-    public int getCdmaRoamingMode() {
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public @CdmaRoamingMode int getCdmaRoamingMode() {
         int mode = CDMA_ROAMING_MODE_RADIO_DEFAULT;
         try {
             ITelephony telephony = getITelephony();
@@ -9002,8 +9013,9 @@
      *
      * @hide
      */
+    @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
-    public boolean setCdmaRoamingMode(int mode) {
+    public boolean setCdmaRoamingMode(@CdmaRoamingMode int mode) {
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
@@ -9015,6 +9027,36 @@
         return false;
     }
 
+    /** @hide */
+    @IntDef(flag = true, prefix = { "CDMA_SUBSCRIPTION_" }, value = {
+            CDMA_SUBSCRIPTION_UNKNOWN,
+            CDMA_SUBSCRIPTION_RUIM_SIM,
+            CDMA_SUBSCRIPTION_NV
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface CdmaSubscription{}
+
+    /** Used for CDMA subscription mode, it'll be UNKNOWN if there is no Subscription source.
+     * @hide
+     */
+    @SystemApi
+    public static final int CDMA_SUBSCRIPTION_UNKNOWN  = -1;
+
+    /** Used for CDMA subscription mode: RUIM/SIM (default)
+     * @hide
+     */
+    @SystemApi
+    public static final int CDMA_SUBSCRIPTION_RUIM_SIM = 0;
+
+    /** Used for CDMA subscription mode: NV -> non-volatile memory
+     * @hide
+     */
+    @SystemApi
+    public static final int CDMA_SUBSCRIPTION_NV       = 1;
+
+    /** @hide */
+    public static final int PREFERRED_CDMA_SUBSCRIPTION = CDMA_SUBSCRIPTION_RUIM_SIM;
+
     /**
      * Sets the subscription mode for CDMA phone to the given mode {@code mode}.
      *
@@ -9022,14 +9064,15 @@
      *
      * @return {@code true} if successed.
      *
-     * @see Phone#CDMA_SUBSCRIPTION_UNKNOWN
-     * @see Phone#CDMA_SUBSCRIPTION_RUIM_SIM
-     * @see Phone#CDMA_SUBSCRIPTION_NV
+     * @see #CDMA_SUBSCRIPTION_UNKNOWN
+     * @see #CDMA_SUBSCRIPTION_RUIM_SIM
+     * @see #CDMA_SUBSCRIPTION_NV
      *
      * @hide
      */
+    @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
-    public boolean setCdmaSubscriptionMode(int mode) {
+    public boolean setCdmaSubscriptionMode(@CdmaSubscription int mode) {
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
@@ -10642,6 +10685,7 @@
      * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}.
      *
      * @param enabled control enable or disable carrier data.
+     * @see #resetAllCarrierActions()
      * @hide
      */
     @SystemApi
@@ -10668,6 +10712,7 @@
      * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}.
      *
      * @param enabled control enable or disable radio.
+     * @see #resetAllCarrierActions()
      * @hide
      */
     @SystemApi
@@ -10694,6 +10739,7 @@
      * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}.
      *
      * @param report control start/stop reporting network status.
+     * @see #resetAllCarrierActions()
      * @hide
      */
     @SystemApi
diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java
index 6005f77..9b739d3 100644
--- a/telephony/java/android/telephony/ims/ProvisioningManager.java
+++ b/telephony/java/android/telephony/ims/ProvisioningManager.java
@@ -25,8 +25,6 @@
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.annotation.WorkerThread;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageManager;
 import android.os.Binder;
 import android.os.RemoteException;
 import android.telephony.CarrierConfigManager;
@@ -382,10 +380,6 @@
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public void registerProvisioningChangedCallback(@NonNull @CallbackExecutor Executor executor,
             @NonNull Callback callback) throws ImsException {
-        if (!isImsAvailableOnDevice()) {
-            throw new ImsException("IMS not available on device.",
-                    ImsException.CODE_ERROR_UNSUPPORTED_OPERATION);
-        }
         callback.setExecutor(executor);
         try {
             getITelephony().registerImsProvisioningChangedCallback(mSubId, callback.getBinder());
@@ -597,39 +591,25 @@
     /**
      * Notify the framework that an RCS autoconfiguration XML file has been received for
      * provisioning.
+     * <p>
+     * Requires Permission: Manifest.permission.MODIFY_PHONE_STATE or that the calling app has
+     * carrier privileges (see {@link #hasCarrierPrivileges}).
      * @param config The XML file to be read. ASCII/UTF8 encoded text if not compressed.
      * @param isCompressed The XML file is compressed in gzip format and must be decompressed
      *         before being read.
-     * @hide
+     *
      */
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     public void notifyRcsAutoConfigurationReceived(@NonNull byte[] config, boolean isCompressed) {
         if (config == null) {
             throw new IllegalArgumentException("Must include a non-null config XML file.");
         }
-        // TODO: Connect to ImsConfigImplBase.
-        throw new UnsupportedOperationException("notifyRcsAutoConfigurationReceived is not"
-                + "supported");
-    }
-
-    private static boolean isImsAvailableOnDevice() {
-        IPackageManager pm = IPackageManager.Stub.asInterface(
-                TelephonyFrameworkInitializer
-                        .getTelephonyServiceManager()
-                        .getPackageManagerServiceRegisterer()
-                        .get());
-        if (pm == null) {
-            // For some reason package manger is not available.. This will fail internally anyways,
-            // so do not throw error and allow.
-            return true;
-        }
         try {
-            return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS, 0);
+            getITelephony().notifyRcsAutoConfigurationReceived(mSubId, config, isCompressed);
         } catch (RemoteException e) {
-            // For some reason package manger is not available.. This will fail internally anyways,
-            // so do not throw error and allow.
+            throw e.rethrowAsRuntimeException();
         }
-        return true;
+
     }
 
     private static ITelephony getITelephony() {
diff --git a/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl b/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl
index 53e4596..57206c9 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl
@@ -40,4 +40,5 @@
     // Return result code defined in ImsConfig#OperationStatusConstants
     int setConfigString(int item, String value);
     void updateImsCarrierConfigs(in PersistableBundle bundle);
+    void notifyRcsAutoConfigurationReceived(in byte[] config, boolean isCompressed);
 }
diff --git a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
index 60cf216..6a2638b 100644
--- a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
@@ -17,6 +17,7 @@
 package android.telephony.ims.stub;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.content.Context;
@@ -200,6 +201,12 @@
             }
         }
 
+        @Override
+        public void notifyRcsAutoConfigurationReceived(byte[] config, boolean isCompressed)
+                throws RemoteException {
+            getImsConfigImpl().notifyRcsAutoConfigurationReceived(config, isCompressed);
+        }
+
         private void notifyImsConfigChanged(int item, int value) throws RemoteException {
             getImsConfigImpl().notifyConfigChanged(item, value);
         }
@@ -358,9 +365,9 @@
      * @param config The XML file to be read, if not compressed, it should be in ASCII/UTF8 format.
      * @param isCompressed The XML file is compressed in gzip format and must be decompressed
      *         before being read.
-     * @hide
+     *
      */
-    public void notifyRcsAutoConfigurationReceived(byte[] config, boolean isCompressed) {
+    public void notifyRcsAutoConfigurationReceived(@NonNull byte[] config, boolean isCompressed) {
     }
 
     /**
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 28f3974..cdb95a8 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2137,4 +2137,9 @@
      * Command line command to enable or disable handling of CEP data for test purposes.
      */
     oneway void setCepEnabled(boolean isCepEnabled);
+
+    /**
+     * Notify Rcs auto config received.
+     */
+    void notifyRcsAutoConfigurationReceived(int subId, in byte[] config, boolean isCompressed);
 }
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
index 3877cc1..ce20311 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
@@ -23,12 +23,14 @@
 
 import android.Manifest;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.rollback.RollbackInfo;
 import android.content.rollback.RollbackManager;
 import android.os.ParcelFileDescriptor;
+import android.os.storage.StorageManager;
 import android.provider.DeviceConfig;
 
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -185,12 +187,6 @@
      */
     @Test
     public void testNativeWatchdogTriggersRollback_Phase1() throws Exception {
-        // When multiple staged sessions are installed on a device which doesn't support checkpoint,
-        // only the 1st one will prevail. We have to check no other rollbacks available to ensure
-        // TestApp.A is always the 1st and the only one to commit so rollback can work as intended.
-        // If there are leftover rollbacks from previous tests, this assertion will fail.
-        assertThat(RollbackUtils.getRollbackManager().getAvailableRollbacks()).isEmpty();
-
         Uninstall.packages(TestApp.A);
         Install.single(TestApp.A1).commit();
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
@@ -220,6 +216,64 @@
                 TestApp.A)).isNotNull();
     }
 
+    /**
+     * Stage install an apk with rollback that will be later triggered by unattributable crash.
+     */
+    @Test
+    public void testNativeWatchdogTriggersRollbackForAll_Phase1() throws Exception {
+        Uninstall.packages(TestApp.A);
+        Install.single(TestApp.A1).commit();
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+
+        Install.single(TestApp.A2).setEnableRollback().setStaged().commit();
+    }
+
+    /**
+     * Verify the rollback is available and then install another package with rollback.
+     */
+    @Test
+    public void testNativeWatchdogTriggersRollbackForAll_Phase2() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+        RollbackManager rm = RollbackUtils.getRollbackManager();
+        assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
+                TestApp.A)).isNotNull();
+
+        // Install another package with rollback
+        Uninstall.packages(TestApp.B);
+        Install.single(TestApp.B1).commit();
+        assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1);
+
+        Install.single(TestApp.B2).setEnableRollback().setStaged().commit();
+    }
+
+    /**
+     * Verify the rollbacks are available.
+     */
+    @Test
+    public void testNativeWatchdogTriggersRollbackForAll_Phase3() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+        assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
+        RollbackManager rm = RollbackUtils.getRollbackManager();
+        assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
+                TestApp.A)).isNotNull();
+        assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
+                TestApp.B)).isNotNull();
+    }
+
+    /**
+     * Verify the rollbacks are committed after crashing.
+     */
+    @Test
+    public void testNativeWatchdogTriggersRollbackForAll_Phase4() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1);
+        RollbackManager rm = RollbackUtils.getRollbackManager();
+        assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
+                TestApp.A)).isNotNull();
+        assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
+                TestApp.B)).isNotNull();
+    }
+
     @Test
     public void testNetworkFailedRollback_Phase1() throws Exception {
         // Remove available rollbacks and uninstall NetworkStack on /data/
@@ -438,6 +492,7 @@
         RollbackManager rm = RollbackUtils.getRollbackManager();
         rm.getAvailableRollbacks().stream().flatMap(info -> info.getPackages().stream())
                 .map(info -> info.getPackageName()).forEach(rm::expireRollbackForPackage);
+        assertThat(RollbackUtils.getRollbackManager().getAvailableRollbacks()).isEmpty();
     }
 
     private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test";
@@ -498,4 +553,11 @@
                 .executeShellCommand(cmd);
         IoUtils.closeQuietly(pfd);
     }
+
+    @Test
+    public void isCheckpointSupported() {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
+        assertThat(sm.isCheckpointSupported()).isTrue();
+    }
 }
diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
index 6daa6bc..181e29a 100644
--- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
+++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
@@ -17,6 +17,7 @@
 package com.android.tests.rollback.host;
 
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 import static org.testng.Assert.assertThrows;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
@@ -62,6 +63,7 @@
                 "rm -f /system/apex/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex "
                         + "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex");
         getDevice().reboot();
+        runPhase("testCleanUp");
     }
 
     @After
@@ -95,7 +97,6 @@
 
     @Test
     public void testNativeWatchdogTriggersRollback() throws Exception {
-        //Stage install ModuleMetadata package - this simulates a Mainline module update
         runPhase("testNativeWatchdogTriggersRollback_Phase1");
 
         // Reboot device to activate staged package
@@ -121,6 +122,40 @@
         runPhase("testNativeWatchdogTriggersRollback_Phase3");
     }
 
+    @Test
+    public void testNativeWatchdogTriggersRollbackForAll() throws Exception {
+        // This test requires committing multiple staged rollbacks
+        assumeTrue(isCheckpointSupported());
+
+        // Install a package with rollback enabled.
+        runPhase("testNativeWatchdogTriggersRollbackForAll_Phase1");
+        getDevice().reboot();
+
+        // Once previous staged install is applied, install another package
+        runPhase("testNativeWatchdogTriggersRollbackForAll_Phase2");
+        getDevice().reboot();
+
+        // Verify the new staged install has also been applied successfully.
+        runPhase("testNativeWatchdogTriggersRollbackForAll_Phase3");
+
+        // crash system_server enough times to trigger a rollback
+        crashProcess("system_server", NATIVE_CRASHES_THRESHOLD);
+
+        // Rollback should be committed automatically now.
+        // Give time for rollback to be committed. This could take a while,
+        // because we need all of the following to happen:
+        // 1. system_server comes back up and boot completes.
+        // 2. Rollback health observer detects updatable crashing signal.
+        // 3. Staged rollback session becomes ready.
+        // 4. Device actually reboots.
+        // So we give a generous timeout here.
+        assertTrue(getDevice().waitForDeviceNotAvailable(TimeUnit.MINUTES.toMillis(5)));
+        getDevice().waitForDeviceAvailable();
+
+        // verify all available rollbacks have been committed
+        runPhase("testNativeWatchdogTriggersRollbackForAll_Phase4");
+    }
+
     /**
      * Tests failed network health check triggers watchdog staged rollbacks.
      */
@@ -244,4 +279,13 @@
         // Find the NetworkStack path (can be NetworkStack.apk or NetworkStackNext.apk)
         return getDevice().executeShellCommand("ls /system/priv-app/NetworkStack*/*.apk");
     }
+
+    private boolean isCheckpointSupported() throws Exception {
+        try {
+            runPhase("isCheckpointSupported");
+            return true;
+        } catch (AssertionError ignore) {
+            return false;
+        }
+    }
 }
diff --git a/tests/net/common/java/android/net/LinkPropertiesTest.java b/tests/net/common/java/android/net/LinkPropertiesTest.java
index a7328ac..6005cc3 100644
--- a/tests/net/common/java/android/net/LinkPropertiesTest.java
+++ b/tests/net/common/java/android/net/LinkPropertiesTest.java
@@ -27,8 +27,8 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import android.net.LinkProperties.CompareResult;
 import android.net.LinkProperties.ProvisioningChange;
+import android.net.util.LinkPropertiesUtils.CompareResult;
 import android.system.OsConstants;
 import android.util.ArraySet;
 
diff --git a/tests/net/java/android/net/MacAddressTest.java b/tests/net/java/android/net/MacAddressTest.java
index daf187d..91c9a2a 100644
--- a/tests/net/java/android/net/MacAddressTest.java
+++ b/tests/net/java/android/net/MacAddressTest.java
@@ -22,6 +22,8 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.net.util.MacAddressUtils;
+
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -122,11 +124,11 @@
 
         for (MacAddress mac : multicastAddresses) {
             String msg = mac.toString() + " expected to be a multicast address";
-            assertTrue(msg, mac.isMulticastAddress());
+            assertTrue(msg, MacAddressUtils.isMulticastAddress(mac));
         }
         for (MacAddress mac : unicastAddresses) {
             String msg = mac.toString() + " expected not to be a multicast address";
-            assertFalse(msg, mac.isMulticastAddress());
+            assertFalse(msg, MacAddressUtils.isMulticastAddress(mac));
         }
     }
 
@@ -156,7 +158,7 @@
     public void testMacAddressConversions() {
         final int iterations = 10000;
         for (int i = 0; i < iterations; i++) {
-            MacAddress mac = MacAddress.createRandomUnicastAddress();
+            MacAddress mac = MacAddressUtils.createRandomUnicastAddress();
 
             String stringRepr = mac.toString();
             byte[] bytesRepr = mac.toByteArray();
@@ -188,7 +190,7 @@
         final String expectedLocalOui = "26:5f:78";
         final MacAddress base = MacAddress.fromString(anotherOui + ":0:0:0");
         for (int i = 0; i < iterations; i++) {
-            MacAddress mac = MacAddress.createRandomUnicastAddress(base, r);
+            MacAddress mac = MacAddressUtils.createRandomUnicastAddress(base, r);
             String stringRepr = mac.toString();
 
             assertTrue(stringRepr + " expected to be a locally assigned address",
@@ -199,7 +201,7 @@
         }
 
         for (int i = 0; i < iterations; i++) {
-            MacAddress mac = MacAddress.createRandomUnicastAddress();
+            MacAddress mac = MacAddressUtils.createRandomUnicastAddress();
             String stringRepr = mac.toString();
 
             assertTrue(stringRepr + " expected to be a locally assigned address",
diff --git a/tests/net/java/android/net/TelephonyNetworkSpecifierTest.java b/tests/net/java/android/net/TelephonyNetworkSpecifierTest.java
new file mode 100644
index 0000000..47afed4
--- /dev/null
+++ b/tests/net/java/android/net/TelephonyNetworkSpecifierTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
+
+import static org.junit.Assert.assertEquals;
+
+import android.telephony.SubscriptionManager;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+/**
+ * Unit test for {@link android.net.TelephonyNetworkSpecifier}.
+ */
+@SmallTest
+public class TelephonyNetworkSpecifierTest {
+    private static final int TEST_SUBID = 5;
+
+    /**
+     * Validate that IllegalArgumentException will be thrown if build TelephonyNetworkSpecifier
+     * without calling {@link TelephonyNetworkSpecifier.Builder#setSubscriptionId(int)}.
+     */
+    @Test
+    public void testBuilderBuildWithDefault() {
+        try {
+            new TelephonyNetworkSpecifier.Builder().build();
+        } catch (IllegalArgumentException iae) {
+            // expected, test pass
+        }
+    }
+
+    /**
+     * Validate that no exception will be thrown even if pass invalid subscription id to
+     * {@link TelephonyNetworkSpecifier.Builder#setSubscriptionId(int)}.
+     */
+    @Test
+    public void testBuilderBuildWithInvalidSubId() {
+        TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier.Builder()
+                .setSubscriptionId(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+                .build();
+        assertEquals(specifier.getSubscriptionId(), SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+    }
+
+    /**
+     * Validate the correctness of TelephonyNetworkSpecifier when provide valid subId.
+     */
+    @Test
+    public void testBuilderBuildWithValidSubId() {
+        final TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier.Builder()
+                .setSubscriptionId(TEST_SUBID)
+                .build();
+        assertEquals(TEST_SUBID, specifier.getSubscriptionId());
+    }
+
+    /**
+     * Validate that parcel marshalling/unmarshalling works.
+     */
+    @Test
+    public void testParcel() {
+        TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier.Builder()
+                .setSubscriptionId(TEST_SUBID)
+                .build();
+        assertParcelSane(specifier, 1 /* fieldCount */);
+    }
+}
diff --git a/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java b/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
index b783467..de1028c 100644
--- a/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
+++ b/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
@@ -51,6 +51,7 @@
 import android.net.NetworkPolicyManager;
 import android.net.NetworkTemplate;
 import android.net.StringNetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
 import android.os.Handler;
 import android.provider.Settings;
 import android.telephony.TelephonyManager;
@@ -229,7 +230,7 @@
         verify(mCM).registerNetworkCallback(any(), networkCallback.capture(), any());
 
         // Simulate callback after capability changes
-        final NetworkCapabilities capabilities = new NetworkCapabilities()
+        NetworkCapabilities capabilities = new NetworkCapabilities()
                 .addCapability(NET_CAPABILITY_INTERNET)
                 .addTransportType(TRANSPORT_CELLULAR)
                 .setNetworkSpecifier(new StringNetworkSpecifier("234"));
@@ -239,6 +240,19 @@
         networkCallback.getValue().onCapabilitiesChanged(
                 TEST_NETWORK,
                 capabilities);
+
+        // make sure it also works with the new introduced  TelephonyNetworkSpecifier
+        capabilities = new NetworkCapabilities()
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .addTransportType(TRANSPORT_CELLULAR)
+                .setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
+                        .setSubscriptionId(234).build());
+        if (!roaming) {
+            capabilities.addCapability(NET_CAPABILITY_NOT_ROAMING);
+        }
+        networkCallback.getValue().onCapabilitiesChanged(
+                TEST_NETWORK,
+                capabilities);
     }
 
     @Test
diff --git a/wifi/Android.bp b/wifi/Android.bp
index 70c9bef..791da30 100644
--- a/wifi/Android.bp
+++ b/wifi/Android.bp
@@ -97,7 +97,6 @@
     },
     hostdex: true, // for hiddenapi check
     visibility: [
-        "//frameworks/base", // TODO(b/140299412) remove once all dependencies are fixed
         "//frameworks/opt/net/wifi/service:__subpackages__",
     ] + test_access_hidden_api_whitelist,
     apex_available: [
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index ee2b575..a9621fc 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -29,6 +29,7 @@
 import android.net.ProxyInfo;
 import android.net.StaticIpConfiguration;
 import android.net.Uri;
+import android.net.util.MacAddressUtils;
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -372,7 +373,6 @@
      * ECDHE_ECDSA
      * ECDHE_RSA
      * </pre>
-     * @hide
      */
     public static class SuiteBCipher {
         private SuiteBCipher() { }
@@ -1166,7 +1166,7 @@
      * @return true if mac is good to use
      */
     public static boolean isValidMacAddressForRandomization(MacAddress mac) {
-        return mac != null && !mac.isMulticastAddress() && mac.isLocallyAssigned()
+        return mac != null && !MacAddressUtils.isMulticastAddress(mac) && mac.isLocallyAssigned()
                 && !MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS).equals(mac);
     }
 
diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
index 8689a38..0ef75aa 100644
--- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
@@ -27,6 +27,7 @@
 import static org.junit.Assert.assertTrue;
 
 import android.net.MacAddress;
+import android.net.util.MacAddressUtils;
 import android.net.wifi.WifiConfiguration.KeyMgmt;
 import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
 import android.os.Parcel;
@@ -63,7 +64,7 @@
         config.updateIdentifier = "1234";
         config.fromWifiNetworkSpecifier = true;
         config.fromWifiNetworkSuggestion = true;
-        config.setRandomizedMacAddress(MacAddress.createRandomUnicastAddress());
+        config.setRandomizedMacAddress(MacAddressUtils.createRandomUnicastAddress());
         MacAddress macBeforeParcel = config.getRandomizedMacAddress();
         Parcel parcelW = Parcel.obtain();
         config.writeToParcel(parcelW, 0);
@@ -169,7 +170,7 @@
         MacAddress defaultMac = MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS);
         assertEquals(defaultMac, config.getRandomizedMacAddress());
 
-        MacAddress macToChangeInto = MacAddress.createRandomUnicastAddress();
+        MacAddress macToChangeInto = MacAddressUtils.createRandomUnicastAddress();
         config.setRandomizedMacAddress(macToChangeInto);
         MacAddress macAfterChange = config.getRandomizedMacAddress();