Merge "[Passpoint] Truncate IMSI from the SimCredential toString method"
diff --git a/Android.bp b/Android.bp
index df852bd..412099d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -372,6 +372,7 @@
         "devicepolicyprotosnano",
 
         "com.android.sysprop.apex",
+        "com.android.sysprop.init",
         "PlatformProperties",
     ],
     sdk_version: "core_platform",
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
index 08b1c2b..abf78c6 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
@@ -56,6 +56,12 @@
  * instantiate this class directly; instead, retrieve it through
  * {@link android.content.Context#getSystemService
  * Context.getSystemService(Context.JOB_SCHEDULER_SERVICE)}.
+ *
+ * <p class="caution"><strong>Note:</strong> Beginning with API 30
+ * ({@link android.os.Build.VERSION_CODES#R}), JobScheduler will throttle runaway applications.
+ * Calling {@link #schedule(JobInfo)} and other such methods with very high frequency is indicative
+ * of an app bug and so, to make sure the system doesn't get overwhelmed, JobScheduler will begin
+ * to throttle apps that show buggy behavior, regardless of target SDK version.
  */
 @SystemService(Context.JOB_SCHEDULER_SERVICE)
 public abstract class JobScheduler {
diff --git a/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java b/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java
index 4ffcf8a..4b4fb96 100644
--- a/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java
+++ b/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java
@@ -80,27 +80,22 @@
     }
 
     /**
-     * Add the specified package to the power save whitelist.
-     *
-     * @return true if the package was successfully added to the whitelist
+     * Add the specified package to the permanent power save whitelist.
      */
     @RequiresPermission(android.Manifest.permission.DEVICE_POWER)
-    public boolean addToWhitelist(@NonNull String packageName) {
-        return addToWhitelist(Collections.singletonList(packageName)) == 1;
+    public void addToWhitelist(@NonNull String packageName) {
+        addToWhitelist(Collections.singletonList(packageName));
     }
 
     /**
-     * Add the specified packages to the power save whitelist.
-     *
-     * @return the number of packages that were successfully added to the whitelist
+     * Add the specified packages to the permanent power save whitelist.
      */
     @RequiresPermission(android.Manifest.permission.DEVICE_POWER)
-    public int addToWhitelist(@NonNull List<String> packageNames) {
+    public void addToWhitelist(@NonNull List<String> packageNames) {
         try {
-            return mService.addPowerSaveWhitelistApps(packageNames);
+            mService.addPowerSaveWhitelistApps(packageNames);
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
-            return 0;
         }
     }
 
diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
index d2d942a..dc72d6d 100644
--- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
@@ -85,6 +85,7 @@
     /**
      * Checks if an app has been idle for a while and filters out apps that are excluded.
      * It returns false if the current system state allows all apps to be considered active.
+     * This happens if the device is plugged in or otherwise temporarily allowed to make exceptions.
      * Called by interface impls.
      */
     boolean isAppIdleFiltered(String packageName, int appId, int userId,
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 b516279..e4c6b52 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -37,8 +37,6 @@
 import android.app.job.JobWorkItem;
 import android.app.usage.UsageStatsManager;
 import android.app.usage.UsageStatsManagerInternal;
-import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledAfter;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -57,7 +55,6 @@
 import android.os.BatteryStats;
 import android.os.BatteryStatsInternal;
 import android.os.Binder;
-import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -90,7 +87,6 @@
 import com.android.server.DeviceIdleInternal;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
-import com.android.server.compat.PlatformCompat;
 import com.android.server.job.JobSchedulerServiceDumpProto.ActiveJob;
 import com.android.server.job.JobSchedulerServiceDumpProto.PendingJob;
 import com.android.server.job.controllers.BackgroundJobsController;
@@ -155,16 +151,6 @@
     /** The maximum number of jobs that we allow an unprivileged app to schedule */
     private static final int MAX_JOBS_PER_APP = 100;
 
-    /**
-     * {@link #schedule(JobInfo)}, {@link #scheduleAsPackage(JobInfo, String, int, String)}, and
-     * {@link #enqueue(JobInfo, JobWorkItem)} will throw a {@link IllegalStateException} if the app
-     * calls the APIs too frequently.
-     */
-    @ChangeId
-    // This means the change will be enabled for target SDK larger than 29 (Q), meaning R and up.
-    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
-    protected static final long CRASH_ON_EXCEEDED_LIMIT = 144363383L;
-
     @VisibleForTesting
     public static Clock sSystemClock = Clock.systemUTC();
 
@@ -264,7 +250,6 @@
 
     private final CountQuotaTracker mQuotaTracker;
     private static final String QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG = ".schedulePersisted()";
-    private final PlatformCompat mPlatformCompat;
 
     /**
      * Queue of pending jobs. The JobServiceContext class will receive jobs from this list
@@ -986,9 +971,7 @@
                 Slog.e(TAG, userId + "-" + pkg + " has called schedule() too many times");
                 mAppStandbyInternal.restrictApp(
                         pkg, userId, UsageStatsManager.REASON_SUB_RESTRICT_BUGGY);
-                if (mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION
-                        && mPlatformCompat.isChangeEnabledByPackageName(
-                                CRASH_ON_EXCEEDED_LIMIT, pkg, userId)) {
+                if (mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION) {
                     final boolean isDebuggable;
                     synchronized (mLock) {
                         if (!mDebuggableApps.containsKey(packageName)) {
@@ -1370,8 +1353,6 @@
         // Set up the app standby bucketing tracker
         mStandbyTracker = new StandbyTracker();
         mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
-        mPlatformCompat =
-                (PlatformCompat) ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE);
         mQuotaTracker = new CountQuotaTracker(context, Categorizer.SINGLE_CATEGORIZER);
         mQuotaTracker.setCountLimit(Category.SINGLE_CATEGORY,
                 mConstants.API_QUOTA_SCHEDULE_COUNT,
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index f1bfa04..e343478 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -48,6 +48,7 @@
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
 
+import static com.android.server.SystemService.PHASE_BOOT_COMPLETED;
 import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
 
 import android.annotation.NonNull;
@@ -71,9 +72,8 @@
 import android.database.ContentObserver;
 import android.hardware.display.DisplayManager;
 import android.net.ConnectivityManager;
-import android.net.Network;
-import android.net.NetworkRequest;
 import android.net.NetworkScoreManager;
+import android.os.BatteryManager;
 import android.os.BatteryStats;
 import android.os.Build;
 import android.os.Environment;
@@ -285,6 +285,7 @@
     long mInitialForegroundServiceStartTimeoutMillis;
 
     private volatile boolean mAppIdleEnabled;
+    private boolean mIsCharging;
     private boolean mSystemServicesReady = false;
     // There was a system update, defaults need to be initialized after services are ready
     private boolean mPendingInitializeDefaults;
@@ -360,6 +361,11 @@
         mHandler = new AppStandbyHandler(mInjector.getLooper());
         mPackageManager = mContext.getPackageManager();
 
+        DeviceStateReceiver deviceStateReceiver = new DeviceStateReceiver();
+        IntentFilter deviceStates = new IntentFilter(BatteryManager.ACTION_CHARGING);
+        deviceStates.addAction(BatteryManager.ACTION_DISCHARGING);
+        mContext.registerReceiver(deviceStateReceiver, deviceStates);
+
         synchronized (mAppIdleLock) {
             mAppIdleHistory = new AppIdleHistory(mInjector.getDataSystemDirectory(),
                     mInjector.elapsedRealtime());
@@ -417,6 +423,8 @@
             if (mPendingOneTimeCheckIdleStates) {
                 postOneTimeCheckIdleStates();
             }
+        } else if (phase == PHASE_BOOT_COMPLETED) {
+            setChargingState(mInjector.isCharging());
         }
     }
 
@@ -515,6 +523,16 @@
                 appUsage.bucketingReason, false);
     }
 
+    @VisibleForTesting
+    void setChargingState(boolean isCharging) {
+        synchronized (mAppIdleLock) {
+            if (mIsCharging != isCharging) {
+                if (DEBUG) Slog.d(TAG, "Setting mIsCharging to " + isCharging);
+                mIsCharging = isCharging;
+            }
+        }
+    }
+
     @Override
     public void postCheckIdleStates(int userId) {
         mHandler.sendMessage(mHandler.obtainMessage(MSG_CHECK_IDLE_STATES, userId, 0));
@@ -977,6 +995,11 @@
         if (isAppSpecial(packageName, appId, userId)) {
             return false;
         } else {
+            synchronized (mAppIdleLock) {
+                if (!mAppIdleEnabled || mIsCharging) {
+                    return false;
+                }
+            }
             return isAppIdleUnfiltered(packageName, userId, elapsedRealtime);
         }
     }
@@ -1543,6 +1566,8 @@
 
         pw.println();
         pw.print("mAppIdleEnabled="); pw.print(mAppIdleEnabled);
+        pw.print(" mIsCharging=");
+        pw.print(mIsCharging);
         pw.println();
         pw.print("mScreenThresholds="); pw.println(Arrays.toString(mAppStandbyScreenThresholds));
         pw.print("mElapsedThresholds="); pw.println(Arrays.toString(mAppStandbyElapsedThresholds));
@@ -1560,6 +1585,7 @@
         private final Looper mLooper;
         private IDeviceIdleController mDeviceIdleController;
         private IBatteryStats mBatteryStats;
+        private BatteryManager mBatteryManager;
         private PackageManagerInternal mPackageManagerInternal;
         private DisplayManager mDisplayManager;
         private PowerManager mPowerManager;
@@ -1593,6 +1619,7 @@
                 mDisplayManager = (DisplayManager) mContext.getSystemService(
                         Context.DISPLAY_SERVICE);
                 mPowerManager = mContext.getSystemService(PowerManager.class);
+                mBatteryManager = mContext.getSystemService(BatteryManager.class);
 
                 final ActivityManager activityManager =
                         (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
@@ -1630,6 +1657,10 @@
             return buildFlag && runtimeFlag;
         }
 
+        boolean isCharging() {
+            return mBatteryManager.isCharging();
+        }
+
         boolean isPowerSaveWhitelistExceptIdleApp(String packageName) throws RemoteException {
             return mDeviceIdleController.isPowerSaveWhitelistExceptIdleApp(packageName);
         }
@@ -1766,15 +1797,19 @@
         }
     };
 
-    private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder().build();
-
-    private final ConnectivityManager.NetworkCallback mNetworkCallback
-            = new ConnectivityManager.NetworkCallback() {
+    private class DeviceStateReceiver extends BroadcastReceiver {
         @Override
-        public void onAvailable(Network network) {
-            mConnectivityManager.unregisterNetworkCallback(this);
+        public void onReceive(Context context, Intent intent) {
+            switch (intent.getAction()) {
+                case BatteryManager.ACTION_CHARGING:
+                    setChargingState(true);
+                    break;
+                case BatteryManager.ACTION_DISCHARGING:
+                    setChargingState(false);
+                    break;
+            }
         }
-    };
+    }
 
     private final DisplayManager.DisplayListener mDisplayListener
             = new DisplayManager.DisplayListener() {
diff --git a/apex/media/framework/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java
index 3eed26b..7d18578 100644
--- a/apex/media/framework/java/android/media/MediaParser.java
+++ b/apex/media/framework/java/android/media/MediaParser.java
@@ -50,6 +50,7 @@
 import com.google.android.exoplayer2.upstream.DataSpec;
 import com.google.android.exoplayer2.upstream.TransferListener;
 import com.google.android.exoplayer2.util.ParsableByteArray;
+import com.google.android.exoplayer2.video.ColorInfo;
 
 import java.io.EOFException;
 import java.io.IOException;
@@ -810,19 +811,17 @@
     // Private static methods.
 
     private static MediaFormat toMediaFormat(Format format) {
-
         MediaFormat result = new MediaFormat();
-        if (format.bitrate != Format.NO_VALUE) {
-            result.setInteger(MediaFormat.KEY_BIT_RATE, format.bitrate);
-        }
-        if (format.channelCount != Format.NO_VALUE) {
-            result.setInteger(MediaFormat.KEY_CHANNEL_COUNT, format.channelCount);
-        }
+        setOptionalMediaFormatInt(result, MediaFormat.KEY_BIT_RATE, format.bitrate);
+        setOptionalMediaFormatInt(result, MediaFormat.KEY_CHANNEL_COUNT, format.channelCount);
 
-        if (format.colorInfo != null) {
-            result.setInteger(MediaFormat.KEY_COLOR_TRANSFER, format.colorInfo.colorTransfer);
-            result.setInteger(MediaFormat.KEY_COLOR_RANGE, format.colorInfo.colorRange);
-            result.setInteger(MediaFormat.KEY_COLOR_STANDARD, format.colorInfo.colorSpace);
+        ColorInfo colorInfo = format.colorInfo;
+        if (colorInfo != null) {
+            setOptionalMediaFormatInt(
+                    result, MediaFormat.KEY_COLOR_TRANSFER, colorInfo.colorTransfer);
+            setOptionalMediaFormatInt(result, MediaFormat.KEY_COLOR_RANGE, colorInfo.colorRange);
+            setOptionalMediaFormatInt(result, MediaFormat.KEY_COLOR_STANDARD, colorInfo.colorSpace);
+
             if (format.colorInfo.hdrStaticInfo != null) {
                 result.setByteBuffer(
                         MediaFormat.KEY_HDR_STATIC_INFO,
@@ -830,63 +829,50 @@
             }
         }
 
-        if (format.sampleMimeType != null) {
-            result.setString(MediaFormat.KEY_MIME, format.sampleMimeType);
-        }
-        if (format.codecs != null) {
-            result.setString(MediaFormat.KEY_CODECS_STRING, format.codecs);
-        }
+        setOptionalMediaFormatString(result, MediaFormat.KEY_MIME, format.sampleMimeType);
+        setOptionalMediaFormatString(result, MediaFormat.KEY_CODECS_STRING, format.codecs);
         if (format.frameRate != Format.NO_VALUE) {
             result.setFloat(MediaFormat.KEY_FRAME_RATE, format.frameRate);
         }
-        if (format.width != Format.NO_VALUE) {
-            result.setInteger(MediaFormat.KEY_WIDTH, format.width);
-        }
-        if (format.height != Format.NO_VALUE) {
-            result.setInteger(MediaFormat.KEY_HEIGHT, format.height);
-        }
+        setOptionalMediaFormatInt(result, MediaFormat.KEY_WIDTH, format.width);
+        setOptionalMediaFormatInt(result, MediaFormat.KEY_HEIGHT, format.height);
+
         List<byte[]> initData = format.initializationData;
         if (initData != null) {
             for (int i = 0; i < initData.size(); i++) {
                 result.setByteBuffer("csd-" + i, ByteBuffer.wrap(initData.get(i)));
             }
         }
-        if (format.language != null) {
-            result.setString(MediaFormat.KEY_LANGUAGE, format.language);
-        }
-        if (format.maxInputSize != Format.NO_VALUE) {
-            result.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, format.maxInputSize);
-        }
-        if (format.pcmEncoding != Format.NO_VALUE) {
-            result.setInteger(MediaFormat.KEY_PCM_ENCODING, format.pcmEncoding);
-        }
-        if (format.rotationDegrees != Format.NO_VALUE) {
-            result.setInteger(MediaFormat.KEY_ROTATION, format.rotationDegrees);
-        }
-        if (format.sampleRate != Format.NO_VALUE) {
-            result.setInteger(MediaFormat.KEY_SAMPLE_RATE, format.sampleRate);
-        }
+        setOptionalMediaFormatString(result, MediaFormat.KEY_LANGUAGE, format.language);
+        setOptionalMediaFormatInt(result, MediaFormat.KEY_MAX_INPUT_SIZE, format.maxInputSize);
+        setOptionalMediaFormatInt(result, MediaFormat.KEY_PCM_ENCODING, format.pcmEncoding);
+        setOptionalMediaFormatInt(result, MediaFormat.KEY_ROTATION, format.rotationDegrees);
+        setOptionalMediaFormatInt(result, MediaFormat.KEY_SAMPLE_RATE, format.sampleRate);
+
         int selectionFlags = format.selectionFlags;
-        if ((selectionFlags & C.SELECTION_FLAG_AUTOSELECT) != 0) {
-            result.setInteger(MediaFormat.KEY_IS_AUTOSELECT, 1);
+        result.setInteger(
+                MediaFormat.KEY_IS_AUTOSELECT, selectionFlags & C.SELECTION_FLAG_AUTOSELECT);
+        result.setInteger(MediaFormat.KEY_IS_DEFAULT, selectionFlags & C.SELECTION_FLAG_DEFAULT);
+        result.setInteger(
+                MediaFormat.KEY_IS_FORCED_SUBTITLE, selectionFlags & C.SELECTION_FLAG_FORCED);
+
+        setOptionalMediaFormatInt(result, MediaFormat.KEY_ENCODER_DELAY, format.encoderDelay);
+        setOptionalMediaFormatInt(result, MediaFormat.KEY_ENCODER_PADDING, format.encoderPadding);
+
+        if (format.pixelWidthHeightRatio != Format.NO_VALUE && format.pixelWidthHeightRatio != 0) {
+            int parWidth = 1;
+            int parHeight = 1;
+            if (format.pixelWidthHeightRatio < 1.0f) {
+                parHeight = 1 << 30;
+                parWidth = (int) (format.pixelWidthHeightRatio * parHeight);
+            } else if (format.pixelWidthHeightRatio > 1.0f) {
+                parWidth = 1 << 30;
+                parHeight = (int) (parWidth / format.pixelWidthHeightRatio);
+            }
+            result.setInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_WIDTH, parWidth);
+            result.setInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_HEIGHT, parHeight);
+            result.setFloat("pixel-width-height-ratio-float", format.pixelWidthHeightRatio);
         }
-        if ((selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0) {
-            result.setInteger(MediaFormat.KEY_IS_DEFAULT, 1);
-        }
-        if ((selectionFlags & C.SELECTION_FLAG_FORCED) != 0) {
-            result.setInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE, 1);
-        }
-        if (format.encoderDelay != Format.NO_VALUE) {
-            result.setInteger(MediaFormat.KEY_ENCODER_DELAY, format.encoderDelay);
-        }
-        if (format.encoderPadding != Format.NO_VALUE) {
-            result.setInteger(MediaFormat.KEY_ENCODER_PADDING, format.encoderPadding);
-        }
-        // TODO: Implement float to fraction conversion.
-        // if (format.pixelWidthHeightRatio != Format.NO_VALUE) {
-        //     result.setInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_WIDTH, );
-        //     result.setInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_HEIGHT, );
-        // }
 
         // LACK OF SUPPORT FOR:
         //    format.accessibilityChannel;
@@ -899,6 +885,19 @@
         return result;
     }
 
+    private static void setOptionalMediaFormatInt(MediaFormat mediaFormat, String key, int value) {
+        if (value != Format.NO_VALUE) {
+            mediaFormat.setInteger(key, value);
+        }
+    }
+
+    private static void setOptionalMediaFormatString(
+            MediaFormat mediaFormat, String key, @Nullable String value) {
+        if (value != null) {
+            mediaFormat.setString(key, value);
+        }
+    }
+
     private static DrmInitData toFrameworkDrmInitData(
             com.google.android.exoplayer2.drm.DrmInitData drmInitData) {
         // TODO: Implement.
diff --git a/apex/statsd/Android.bp b/apex/statsd/Android.bp
index 1f9f18c..c0f84a0 100644
--- a/apex/statsd/Android.bp
+++ b/apex/statsd/Android.bp
@@ -20,6 +20,7 @@
 
 apex_defaults {
     native_shared_libs: [
+        "libstatspull",
         "libstats_jni",
     ],
     // binaries: ["vold"],
@@ -28,6 +29,7 @@
         "service-statsd",
     ],
     // prebuilts: ["my_prebuilt"],
+    compile_multilib: "both",
     name: "com.android.os.statsd-defaults",
     key: "com.android.os.statsd.key",
     certificate: ":com.android.os.statsd.certificate",
diff --git a/apex/statsd/aidl/Android.bp b/apex/statsd/aidl/Android.bp
index 7c93bc7..4ccdd7e 100644
--- a/apex/statsd/aidl/Android.bp
+++ b/apex/statsd/aidl/Android.bp
@@ -38,6 +38,10 @@
         },
         ndk: {
             enabled: true,
+            apex_available: [
+                "com.android.os.statsd",
+            ],
         }
-    }
+
+    },
 }
diff --git a/apex/statsd/framework/Android.bp b/apex/statsd/framework/Android.bp
index 63a853a..ab669d4 100644
--- a/apex/statsd/framework/Android.bp
+++ b/apex/statsd/framework/Android.bp
@@ -12,6 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    default_visibility: [ ":__pkg__" ]
+}
+
 genrule {
     name: "statslog-statsd-java-gen",
     tools: ["stats-log-api-gen"],
@@ -25,6 +29,9 @@
     srcs: [
         ":statslog-statsd-java-gen",
     ],
+    visibility: [
+        "//cts/hostsidetests/statsd/apps:__subpackages__",
+    ]
 }
 
 filegroup {
@@ -34,6 +41,9 @@
         ":framework-statsd-aidl-sources",
         ":statslog-statsd-java-gen",
     ],
+    visibility: [
+        "//frameworks/base", // For the "global" stubs.
+    ],
 }
 
 java_defaults {
@@ -139,6 +149,10 @@
         "framework-statsd-defaults",
     ],
     srcs: [ ":framework-statsd-stubs-srcs-publicapi" ],
+    visibility: [
+        "//frameworks/base", // Framework
+        "//frameworks/base/apex/statsd", // statsd apex
+    ]
 }
 
 java_library {
@@ -147,6 +161,10 @@
         "framework-statsd-defaults",
     ],
     srcs: [ ":framework-statsd-stubs-srcs-systemapi" ],
+    visibility: [
+        "//frameworks/base", // Framework
+        "//frameworks/base/apex/statsd", // statsd apex
+    ]
 }
 
 java_library {
@@ -155,4 +173,9 @@
         "framework-statsd-defaults",
     ],
     srcs: [ ":framework-statsd-stubs-srcs-module_libs_api" ],
+    visibility: [
+        "//frameworks/base", // Framework
+        "//frameworks/base/apex/statsd", // statsd apex
+        "//frameworks/opt/net/wifi/service" // wifi service
+    ]
 }
diff --git a/api/current.txt b/api/current.txt
index 611fec3..4887c66 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6917,6 +6917,7 @@
     method public boolean grantKeyPairToApp(@Nullable android.content.ComponentName, @NonNull String, @NonNull String);
     method public boolean hasCaCertInstalled(@Nullable android.content.ComponentName, byte[]);
     method public boolean hasGrantedPolicy(@NonNull android.content.ComponentName, int);
+    method public boolean hasLockdownAdminConfiguredNetworks(@NonNull android.content.ComponentName);
     method public boolean installCaCert(@Nullable android.content.ComponentName, byte[]);
     method public boolean installExistingPackage(@NonNull android.content.ComponentName, String);
     method public boolean installKeyPair(@Nullable android.content.ComponentName, @NonNull java.security.PrivateKey, @NonNull java.security.cert.Certificate, @NonNull String);
@@ -6935,7 +6936,6 @@
     method public boolean isDeviceOwnerApp(String);
     method public boolean isEphemeralUser(@NonNull android.content.ComponentName);
     method public boolean isLockTaskPermitted(String);
-    method public boolean isLockdownAdminConfiguredNetworks(@NonNull android.content.ComponentName);
     method public boolean isLogoutEnabled();
     method public boolean isManagedProfile(@NonNull android.content.ComponentName);
     method public boolean isMasterVolumeMuted(@NonNull android.content.ComponentName);
@@ -6982,6 +6982,7 @@
     method public void setCameraDisabled(@NonNull android.content.ComponentName, boolean);
     method @Deprecated public void setCertInstallerPackage(@NonNull android.content.ComponentName, @Nullable String) throws java.lang.SecurityException;
     method public void setCommonCriteriaModeEnabled(@NonNull android.content.ComponentName, boolean);
+    method public void setConfiguredNetworksLockdownState(@NonNull android.content.ComponentName, boolean);
     method public void setCrossProfileCalendarPackages(@NonNull android.content.ComponentName, @Nullable java.util.Set<java.lang.String>);
     method public void setCrossProfileCallerIdDisabled(@NonNull android.content.ComponentName, boolean);
     method public void setCrossProfileContactsSearchDisabled(@NonNull android.content.ComponentName, boolean);
@@ -7001,7 +7002,6 @@
     method public void setLocationEnabled(@NonNull android.content.ComponentName, boolean);
     method public void setLockTaskFeatures(@NonNull android.content.ComponentName, int);
     method public void setLockTaskPackages(@NonNull android.content.ComponentName, @NonNull String[]) throws java.lang.SecurityException;
-    method public void setLockdownAdminConfiguredNetworks(@NonNull android.content.ComponentName, boolean);
     method public void setLogoutEnabled(@NonNull android.content.ComponentName, boolean);
     method public void setLongSupportMessage(@NonNull android.content.ComponentName, @Nullable CharSequence);
     method public void setManagedProfileMaximumTimeOff(@NonNull android.content.ComponentName, long);
@@ -12703,8 +12703,7 @@
 
   public class Resources {
     ctor @Deprecated public Resources(android.content.res.AssetManager, android.util.DisplayMetrics, android.content.res.Configuration);
-    method public void addLoader(@NonNull android.content.res.loader.ResourcesLoader);
-    method public void clearLoaders();
+    method public void addLoaders(@NonNull android.content.res.loader.ResourcesLoader...);
     method public final void finishPreloading();
     method public final void flushLayoutCache();
     method @NonNull public android.content.res.XmlResourceParser getAnimation(@AnimRes @AnimatorRes int) throws android.content.res.Resources.NotFoundException;
@@ -12731,7 +12730,6 @@
     method @NonNull public int[] getIntArray(@ArrayRes int) throws android.content.res.Resources.NotFoundException;
     method public int getInteger(@IntegerRes int) throws android.content.res.Resources.NotFoundException;
     method @NonNull public android.content.res.XmlResourceParser getLayout(@LayoutRes int) throws android.content.res.Resources.NotFoundException;
-    method @NonNull public java.util.List<android.content.res.loader.ResourcesLoader> getLoaders();
     method @Deprecated public android.graphics.Movie getMovie(@RawRes int) throws android.content.res.Resources.NotFoundException;
     method @NonNull public String getQuantityString(@PluralsRes int, int, java.lang.Object...) throws android.content.res.Resources.NotFoundException;
     method @NonNull public String getQuantityString(@PluralsRes int, int) throws android.content.res.Resources.NotFoundException;
@@ -12759,8 +12757,7 @@
     method public android.content.res.AssetFileDescriptor openRawResourceFd(@RawRes int) throws android.content.res.Resources.NotFoundException;
     method public void parseBundleExtra(String, android.util.AttributeSet, android.os.Bundle) throws org.xmlpull.v1.XmlPullParserException;
     method public void parseBundleExtras(android.content.res.XmlResourceParser, android.os.Bundle) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
-    method public void removeLoader(@NonNull android.content.res.loader.ResourcesLoader);
-    method public void setLoaders(@NonNull java.util.List<android.content.res.loader.ResourcesLoader>);
+    method public void removeLoaders(@NonNull android.content.res.loader.ResourcesLoader...);
     method @Deprecated public void updateConfiguration(android.content.res.Configuration, android.util.DisplayMetrics);
     field @AnyRes public static final int ID_NULL = 0; // 0x0
   }
@@ -45514,7 +45511,7 @@
     field public static final int DIRECTION_INCOMING = 0; // 0x0
     field public static final int DIRECTION_OUTGOING = 1; // 0x1
     field public static final int DIRECTION_UNKNOWN = -1; // 0xffffffff
-    field public static final int PROPERTY_ASSISTED_DIALING_USED = 512; // 0x200
+    field public static final int PROPERTY_ASSISTED_DIALING = 512; // 0x200
     field public static final int PROPERTY_CONFERENCE = 1; // 0x1
     field public static final int PROPERTY_EMERGENCY_CALLBACK_MODE = 4; // 0x4
     field public static final int PROPERTY_ENTERPRISE_CALL = 32; // 0x20
@@ -45603,7 +45600,8 @@
     method public final java.util.List<android.telecom.Connection> getConferenceableConnections();
     method public final int getConnectionCapabilities();
     method public final int getConnectionProperties();
-    method public final long getConnectionTime();
+    method public final long getConnectionStartElapsedRealtimeMillis();
+    method @IntRange(from=0) public final long getConnectionTime();
     method public final java.util.List<android.telecom.Connection> getConnections();
     method public final android.telecom.DisconnectCause getDisconnectCause();
     method public final android.os.Bundle getExtras();
@@ -45633,8 +45631,9 @@
     method public final void setConferenceableConnections(java.util.List<android.telecom.Connection>);
     method public final void setConnectionCapabilities(int);
     method public final void setConnectionProperties(int);
-    method public final void setConnectionStartElapsedRealTime(long);
-    method public final void setConnectionTime(long);
+    method @Deprecated public final void setConnectionStartElapsedRealTime(long);
+    method public final void setConnectionStartElapsedRealtimeMillis(long);
+    method public final void setConnectionTime(@IntRange(from=0) long);
     method public final void setDialing();
     method public final void setDisconnected(android.telecom.DisconnectCause);
     method public final void setExtras(@Nullable android.os.Bundle);
@@ -45794,7 +45793,7 @@
     field public static final String EXTRA_IS_RTT_AUDIO_PRESENT = "android.telecom.extra.IS_RTT_AUDIO_PRESENT";
     field public static final String EXTRA_LAST_FORWARDED_NUMBER = "android.telecom.extra.LAST_FORWARDED_NUMBER";
     field public static final String EXTRA_SIP_INVITE = "android.telecom.extra.SIP_INVITE";
-    field public static final int PROPERTY_ASSISTED_DIALING_USED = 512; // 0x200
+    field public static final int PROPERTY_ASSISTED_DIALING = 512; // 0x200
     field public static final int PROPERTY_HAS_CDMA_VOICE_PRIVACY = 32; // 0x20
     field public static final int PROPERTY_HIGH_DEF_AUDIO = 4; // 0x4
     field public static final int PROPERTY_IS_EXTERNAL_CALL = 16; // 0x10
@@ -46973,7 +46972,7 @@
     method @NonNull public abstract android.telephony.CellIdentity getCellIdentity();
     method @NonNull public abstract android.telephony.CellSignalStrength getCellSignalStrength();
     method @Deprecated public long getTimeStamp();
-    method public long getTimestampNanos();
+    method public long getTimestampMillis();
     method public boolean isRegistered();
     field public static final int CONNECTION_NONE = 0; // 0x0
     field public static final int CONNECTION_PRIMARY_SERVING = 1; // 0x1
@@ -47133,6 +47132,19 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ClosedSubscriberGroupInfo> CREATOR;
   }
 
+  public final class DisplayInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getNetworkType();
+    method public int getOverrideNetworkType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.DisplayInfo> CREATOR;
+    field public static final int OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO = 2; // 0x2
+    field public static final int OVERRIDE_NETWORK_TYPE_LTE_CA = 1; // 0x1
+    field public static final int OVERRIDE_NETWORK_TYPE_NONE = 0; // 0x0
+    field public static final int OVERRIDE_NETWORK_TYPE_NR_NSA = 3; // 0x3
+    field public static final int OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE = 4; // 0x4
+  }
+
   public class IccOpenLogicalChannelResponse implements android.os.Parcelable {
     method public int describeContents();
     method public int getChannel();
@@ -47389,6 +47401,7 @@
     method public void onDataActivity(int);
     method public void onDataConnectionStateChanged(int);
     method public void onDataConnectionStateChanged(int, int);
+    method @RequiresPermission("android.permission.READ_PHONE_STATE") public void onDisplayInfoChanged(@NonNull android.telephony.DisplayInfo);
     method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onImsCallDisconnectCauseChanged(@NonNull android.telephony.ims.ImsReasonInfo);
     method public void onMessageWaitingIndicatorChanged(boolean);
     method @RequiresPermission("android.permission.MODIFY_PHONE_STATE") public void onPreciseDataConnectionStateChanged(@NonNull android.telephony.PreciseDataConnectionState);
@@ -47406,6 +47419,7 @@
     field public static final int LISTEN_CELL_LOCATION = 16; // 0x10
     field public static final int LISTEN_DATA_ACTIVITY = 128; // 0x80
     field public static final int LISTEN_DATA_CONNECTION_STATE = 64; // 0x40
+    field public static final int LISTEN_DISPLAY_INFO_CHANGED = 1048576; // 0x100000
     field public static final int LISTEN_EMERGENCY_NUMBER_LIST = 16777216; // 0x1000000
     field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_IMS_CALL_DISCONNECT_CAUSES = 134217728; // 0x8000000
     field public static final int LISTEN_MESSAGE_WAITING_INDICATOR = 4; // 0x4
@@ -47488,7 +47502,7 @@
     method @Deprecated public int getGsmBitErrorRate();
     method @Deprecated public int getGsmSignalStrength();
     method public int getLevel();
-    method public long getTimestampNanos();
+    method public long getTimestampMillis();
     method @Deprecated public boolean isGsm();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.SignalStrength> CREATOR;
diff --git a/api/system-current.txt b/api/system-current.txt
index 59cc616..0fd8a20 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -9,6 +9,7 @@
     field public static final String ACCESS_DRM_CERTIFICATES = "android.permission.ACCESS_DRM_CERTIFICATES";
     field @Deprecated public static final String ACCESS_FM_RADIO = "android.permission.ACCESS_FM_RADIO";
     field public static final String ACCESS_INSTANT_APPS = "android.permission.ACCESS_INSTANT_APPS";
+    field public static final String ACCESS_LOCUS_ID_USAGE_STATS = "android.permission.ACCESS_LOCUS_ID_USAGE_STATS";
     field public static final String ACCESS_MESSAGES_ON_ICC = "android.permission.ACCESS_MESSAGES_ON_ICC";
     field public static final String ACCESS_MOCK_LOCATION = "android.permission.ACCESS_MOCK_LOCATION";
     field public static final String ACCESS_MTP = "android.permission.ACCESS_MTP";
@@ -6030,7 +6031,7 @@
 
   public class CaptivePortal implements android.os.Parcelable {
     method public void logEvent(int, @NonNull String);
-    method public void reevaluateNetwork();
+    method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void reevaluateNetwork();
     method public void useNetwork();
     field public static final int APP_REQUEST_REEVALUATION_REQUIRED = 100; // 0x64
     field public static final int APP_RETURN_DISMISSED = 0; // 0x0
@@ -8838,8 +8839,8 @@
   }
 
   public class PowerWhitelistManager {
-    method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public boolean addToWhitelist(@NonNull String);
-    method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public int addToWhitelist(@NonNull java.util.List<java.lang.String>);
+    method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void addToWhitelist(@NonNull String);
+    method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void addToWhitelist(@NonNull java.util.List<java.lang.String>);
     method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public void whitelistAppTemporarily(@NonNull String, long);
     method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public long whitelistAppTemporarilyForEvent(@NonNull String, int, @NonNull String);
     field public static final int EVENT_MMS = 2; // 0x2
@@ -10853,27 +10854,26 @@
   public abstract class Conference extends android.telecom.Conferenceable {
     method @Deprecated public final android.telecom.AudioState getAudioState();
     method @Deprecated public final long getConnectTimeMillis();
-    method public final long getConnectionStartElapsedRealTime();
     method public android.telecom.Connection getPrimaryConnection();
     method @NonNull public final String getTelecomCallId();
     method @Deprecated public void onAudioStateChanged(android.telecom.AudioState);
-    method public final void setAddress(@NonNull android.net.Uri, int);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public final void setAddress(@NonNull android.net.Uri, int);
     method public final void setCallerDisplayName(@NonNull String, int);
-    method public void setConferenceState(boolean);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setConferenceState(boolean);
     method @Deprecated public final void setConnectTimeMillis(long);
   }
 
   public abstract class Connection extends android.telecom.Conferenceable {
     method @Deprecated public final android.telecom.AudioState getAudioState();
-    method public final long getConnectElapsedTimeMillis();
-    method public final long getConnectTimeMillis();
+    method @IntRange(from=0) public final long getConnectTimeMillis();
+    method public final long getConnectionStartElapsedRealtimeMillis();
     method @Nullable public android.telecom.PhoneAccountHandle getPhoneAccountHandle();
     method @Nullable public final String getTelecomCallId();
     method @Deprecated public void onAudioStateChanged(android.telecom.AudioState);
     method public final void resetConnectionTime();
     method public void setCallDirection(int);
-    method public final void setConnectTimeMillis(long);
-    method public final void setConnectionStartElapsedRealTime(long);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public final void setConnectTimeMillis(@IntRange(from=0) long);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public final void setConnectionStartElapsedRealtimeMillis(long);
     method public void setPhoneAccountHandle(@NonNull android.telecom.PhoneAccountHandle);
     method public void setTelecomCallId(@NonNull String);
     field public static final int CAPABILITY_CONFERENCE_HAS_NO_CHILDREN = 2097152; // 0x200000
@@ -11032,7 +11032,7 @@
   }
 
   public static class PhoneAccount.Builder {
-    method @NonNull public android.telecom.PhoneAccount.Builder setGroupId(@NonNull String);
+    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telecom.PhoneAccount.Builder setGroupId(@NonNull String);
   }
 
   public class PhoneAccountSuggestionService extends android.app.Service {
@@ -11107,7 +11107,7 @@
     method public int getCallState();
     method public android.telecom.PhoneAccountHandle getConnectionManager();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getCurrentTtyMode();
-    method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getDefaultDialerPackage(int);
+    method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getDefaultDialerPackage(@NonNull android.os.UserHandle);
     method @Deprecated public android.content.ComponentName getDefaultPhoneApp();
     method public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsForPackage();
     method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsSupportingScheme(String);
@@ -12571,6 +12571,7 @@
     method public void notifyDataActivityChanged(int, int);
     method public void notifyDataConnectionForSubscriber(int, int, int, @Nullable android.telephony.PreciseDataConnectionState);
     method public void notifyDisconnectCause(int, int, int, int);
+    method public void notifyDisplayInfoChanged(int, int, @NonNull android.telephony.DisplayInfo);
     method public void notifyEmergencyNumberList(int, int);
     method public void notifyImsDisconnectCause(int, @NonNull android.telephony.ims.ImsReasonInfo);
     method public void notifyMessageWaitingChanged(int, int, boolean);
diff --git a/api/test-current.txt b/api/test-current.txt
index 7e8eb0c..4c8bb02 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -15,6 +15,7 @@
     field public static final String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS";
     field public static final String MANAGE_CRATES = "android.permission.MANAGE_CRATES";
     field public static final String MANAGE_ROLLBACKS = "android.permission.MANAGE_ROLLBACKS";
+    field public static final String NETWORK_STACK = "android.permission.NETWORK_STACK";
     field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
     field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
     field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS";
@@ -1641,7 +1642,7 @@
 
   public class CaptivePortal implements android.os.Parcelable {
     method public void logEvent(int, @NonNull String);
-    method public void reevaluateNetwork();
+    method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void reevaluateNetwork();
     method public void useNetwork();
     field public static final int APP_REQUEST_REEVALUATION_REQUIRED = 100; // 0x64
     field public static final int APP_RETURN_DISMISSED = 0; // 0x0
@@ -2450,8 +2451,8 @@
   }
 
   public class PowerWhitelistManager {
-    method @RequiresPermission("android.permission.DEVICE_POWER") public boolean addToWhitelist(@NonNull String);
-    method @RequiresPermission("android.permission.DEVICE_POWER") public int addToWhitelist(@NonNull java.util.List<java.lang.String>);
+    method @RequiresPermission("android.permission.DEVICE_POWER") public void addToWhitelist(@NonNull String);
+    method @RequiresPermission("android.permission.DEVICE_POWER") public void addToWhitelist(@NonNull java.util.List<java.lang.String>);
     method @RequiresPermission("android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST") public void whitelistAppTemporarily(@NonNull String, long);
     method @RequiresPermission("android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST") public long whitelistAppTemporarilyForEvent(@NonNull String, int, @NonNull String);
     field public static final int EVENT_MMS = 2; // 0x2
@@ -3440,23 +3441,22 @@
   }
 
   public abstract class Conference extends android.telecom.Conferenceable {
-    method public final long getConnectionStartElapsedRealTime();
     method public android.telecom.Connection getPrimaryConnection();
     method @NonNull public final String getTelecomCallId();
-    method public final void setAddress(@NonNull android.net.Uri, int);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public final void setAddress(@NonNull android.net.Uri, int);
     method public final void setCallerDisplayName(@NonNull String, int);
-    method public void setConferenceState(boolean);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setConferenceState(boolean);
   }
 
   public abstract class Connection extends android.telecom.Conferenceable {
-    method public final long getConnectElapsedTimeMillis();
-    method public final long getConnectTimeMillis();
+    method @IntRange(from=0) public final long getConnectTimeMillis();
+    method public final long getConnectionStartElapsedRealtimeMillis();
     method @Nullable public android.telecom.PhoneAccountHandle getPhoneAccountHandle();
     method @Nullable public final String getTelecomCallId();
     method public final void resetConnectionTime();
     method public void setCallDirection(int);
-    method public final void setConnectTimeMillis(long);
-    method public final void setConnectionStartElapsedRealTime(long);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public final void setConnectTimeMillis(@IntRange(from=0) long);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public final void setConnectionStartElapsedRealtimeMillis(long);
     method public void setPhoneAccountHandle(@NonNull android.telecom.PhoneAccountHandle);
     method public void setTelecomCallId(@NonNull String);
     field public static final int CAPABILITY_CONFERENCE_HAS_NO_CHILDREN = 2097152; // 0x200000
@@ -3488,7 +3488,7 @@
   }
 
   public static class PhoneAccount.Builder {
-    method @NonNull public android.telecom.PhoneAccount.Builder setGroupId(@NonNull String);
+    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telecom.PhoneAccount.Builder setGroupId(@NonNull String);
   }
 
   public class PhoneAccountSuggestionService extends android.app.Service {
@@ -3502,7 +3502,7 @@
   public class TelecomManager {
     method @NonNull @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public java.util.List<android.telecom.PhoneAccountHandle> getCallCapablePhoneAccounts(boolean);
     method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public int getCurrentTtyMode();
-    method @Nullable @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getDefaultDialerPackage(int);
+    method @Nullable @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getDefaultDialerPackage(@NonNull android.os.UserHandle);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean isInEmergencyCall();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUserSelectedOutgoingPhoneAccount(@Nullable android.telecom.PhoneAccountHandle);
     field public static final int TTY_MODE_FULL = 1; // 0x1
@@ -3720,6 +3720,7 @@
     method public void notifyDataActivityChanged(int, int);
     method public void notifyDataConnectionForSubscriber(int, int, int, @Nullable android.telephony.PreciseDataConnectionState);
     method public void notifyDisconnectCause(int, int, int, int);
+    method public void notifyDisplayInfoChanged(int, int, @NonNull android.telephony.DisplayInfo);
     method public void notifyEmergencyNumberList(int, int);
     method public void notifyImsDisconnectCause(int, @NonNull android.telephony.ims.ImsReasonInfo);
     method public void notifyMessageWaitingChanged(int, int, boolean);
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 1bc235a..03f97d8 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -378,7 +378,7 @@
                 240 [(module) = "framework"];
         BootTimeEventUtcTime boot_time_event_utc_time_reported = 241;
         BootTimeEventErrorCode boot_time_event_error_code_reported = 242 [(module) = "framework"];
-        UserspaceRebootReported userspace_reboot_reported = 243;
+        UserspaceRebootReported userspace_reboot_reported = 243 [(module) = "framework"];
         NotificationReported notification_reported = 244 [(module) = "framework"];
         NotificationPanelReported notification_panel_reported = 245;
         NotificationChannelModified notification_panel_modified = 246;
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 50b9d6b..8b07418 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -109,6 +109,8 @@
 import android.media.soundtrigger.SoundTriggerManager;
 import android.media.tv.ITvInputManager;
 import android.media.tv.TvInputManager;
+import android.media.tv.tuner.ITunerResourceManager;
+import android.media.tv.tuner.TunerResourceManager;
 import android.net.ConnectivityDiagnosticsManager;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityThread;
@@ -937,6 +939,17 @@
                 return new TvInputManager(service, ctx.getUserId());
             }});
 
+        registerService(Context.TV_TUNER_RESOURCE_MGR_SERVICE, TunerResourceManager.class,
+                new CachedServiceFetcher<TunerResourceManager>() {
+            @Override
+            public TunerResourceManager createService(ContextImpl ctx)
+                    throws ServiceNotFoundException {
+                IBinder iBinder =
+                        ServiceManager.getServiceOrThrow(Context.TV_TUNER_RESOURCE_MGR_SERVICE);
+                ITunerResourceManager service = ITunerResourceManager.Stub.asInterface(iBinder);
+                return new TunerResourceManager(service, ctx.getUserId());
+            }});
+
         registerService(Context.NETWORK_SCORE_SERVICE, NetworkScoreManager.class,
                 new CachedServiceFetcher<NetworkScoreManager>() {
             @Override
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 48eab92..4a5a23a 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -4718,15 +4718,40 @@
     public static final int KEYGUARD_DISABLE_FEATURES_ALL = 0x7fffffff;
 
     /**
-     * Keyguard features that when set on a managed profile that doesn't have its own challenge will
-     * affect the profile's parent user. These can also be set on the managed profile's parent
+     * Keyguard features that when set on a non-organization-owned managed profile that doesn't
+     * have its own challenge will affect the profile's parent user. These can also be set on the
+     * managed profile's parent {@link DevicePolicyManager} instance to explicitly control the
+     * parent user.
+     *
+     * <p>
+     * Organization-owned managed profile supports disabling additional keyguard features on the
+     * parent user as defined in {@link #ORG_OWNED_PROFILE_KEYGUARD_FEATURES_PARENT_ONLY}.
+     *
+     * @hide
+     */
+    public static final int NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER =
+            DevicePolicyManager.KEYGUARD_DISABLE_TRUST_AGENTS
+            | DevicePolicyManager.KEYGUARD_DISABLE_BIOMETRICS;
+
+    /**
+     * Keyguard features that when set by the profile owner of an organization-owned managed
+     * profile will affect the profile's parent user if set on the managed profile's parent
      * {@link DevicePolicyManager} instance.
      *
      * @hide
      */
+    public static final int ORG_OWNED_PROFILE_KEYGUARD_FEATURES_PARENT_ONLY =
+            KEYGUARD_DISABLE_SECURE_CAMERA;
+
+    /**
+     * Keyguard features that when set on a normal or organization-owned managed profile, have
+     * the potential to affect the profile's parent user.
+     *
+     * @hide
+     */
     public static final int PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER =
-            DevicePolicyManager.KEYGUARD_DISABLE_TRUST_AGENTS
-            | DevicePolicyManager.KEYGUARD_DISABLE_BIOMETRICS;
+            DevicePolicyManager.NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER
+                    | DevicePolicyManager.ORG_OWNED_PROFILE_KEYGUARD_FEATURES_PARENT_ONLY;
 
     /**
      * @deprecated This method does not actually modify the storage encryption of the device.
@@ -6115,11 +6140,20 @@
      * <li>{@link #KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS} which affects notifications generated
      * by applications in the managed profile.
      * </ul>
+     * <p>
+     * From version {@link android.os.Build.VERSION_CODES#R} the profile owner of an
+     * organization-owned managed profile can set:
+     * <ul>
+     * <li>{@link #KEYGUARD_DISABLE_SECURE_CAMERA} which affects the parent user when called on the
+     * parent profile.
+     * </ul>
      * {@link #KEYGUARD_DISABLE_TRUST_AGENTS}, {@link #KEYGUARD_DISABLE_FINGERPRINT},
-     * {@link #KEYGUARD_DISABLE_FACE} and {@link #KEYGUARD_DISABLE_IRIS} can also be
-     * set on the {@link DevicePolicyManager} instance returned by
-     * {@link #getParentProfileInstance(ComponentName)} in order to set restrictions on the parent
-     * profile.
+     * {@link #KEYGUARD_DISABLE_FACE}, {@link #KEYGUARD_DISABLE_IRIS} and
+     * {@link #KEYGUARD_DISABLE_SECURE_CAMERA} can also be set on the {@link DevicePolicyManager}
+     * instance returned by {@link #getParentProfileInstance(ComponentName)} in order to set
+     * restrictions on the parent profile. {@link #KEYGUARD_DISABLE_SECURE_CAMERA} can only be set
+     * on the parent profile instance if the calling device admin is the profile owner of an
+     * organization-owned managed profile.
      * <p>
      * Requests to disable other features on a managed profile will be ignored.
      * <p>
@@ -8789,11 +8823,11 @@
      * @throws SecurityException if caller is not a device owner or a profile owner of an
      *                           organization-owned managed profile.
      */
-    public void setLockdownAdminConfiguredNetworks(@NonNull ComponentName admin, boolean lockdown) {
-        throwIfParentInstance("setLockdownAdminConfiguredNetworks");
+    public void setConfiguredNetworksLockdownState(@NonNull ComponentName admin, boolean lockdown) {
+        throwIfParentInstance("setConfiguredNetworksLockdownState");
         if (mService != null) {
             try {
-                mService.setLockdownAdminConfiguredNetworks(admin, lockdown);
+                mService.setConfiguredNetworksLockdownState(admin, lockdown);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -8809,11 +8843,11 @@
      * @throws SecurityException if caller is not a device owner or a profile owner of an
      *                           organization-owned managed profile.
      */
-    public boolean isLockdownAdminConfiguredNetworks(@NonNull ComponentName admin) {
-        throwIfParentInstance("setLockdownAdminConfiguredNetworks");
+    public boolean hasLockdownAdminConfiguredNetworks(@NonNull ComponentName admin) {
+        throwIfParentInstance("hasLockdownAdminConfiguredNetworks");
         if (mService != null) {
             try {
-                return mService.isLockdownAdminConfiguredNetworks(admin);
+                return mService.hasLockdownAdminConfiguredNetworks(admin);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 8ef25bd..0aed39c 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -266,8 +266,8 @@
     void setSystemSetting(in ComponentName who, in String setting, in String value);
     void setSecureSetting(in ComponentName who, in String setting, in String value);
 
-    void setLockdownAdminConfiguredNetworks(in ComponentName who, boolean lockdown);
-    boolean isLockdownAdminConfiguredNetworks(in ComponentName who);
+    void setConfiguredNetworksLockdownState(in ComponentName who, boolean lockdown);
+    boolean hasLockdownAdminConfiguredNetworks(in ComponentName who);
 
     void setLocationEnabled(in ComponentName who, boolean locationEnabled);
     void requestSetLocationProviderAllowed(in ComponentName who, in String provider, boolean providerAllowed);
diff --git a/core/java/android/app/role/RoleManager.java b/core/java/android/app/role/RoleManager.java
index ea66fd47..db4f1de 100644
--- a/core/java/android/app/role/RoleManager.java
+++ b/core/java/android/app/role/RoleManager.java
@@ -90,6 +90,7 @@
      * The name of the dialer role.
      *
      * @see Intent#ACTION_DIAL
+     * @see android.telecom.InCallService
      */
     public static final String ROLE_DIALER = "android.app.role.DIALER";
 
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index 5668944..2c701b4 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -599,7 +599,8 @@
     /**
      * Returns whether the specified app is currently considered inactive. This will be true if the
      * app hasn't been used directly or indirectly for a period of time defined by the system. This
-     * could be of the order of several hours or days.
+     * could be of the order of several hours or days. Apps are not considered inactive when the
+     * device is charging.
      * @param packageName The package name of the app to query
      * @return whether the app is currently considered inactive
      */
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 249e582..49f62f4 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3457,6 +3457,7 @@
             CONSUMER_IR_SERVICE,
             //@hide: TRUST_SERVICE,
             TV_INPUT_SERVICE,
+            //@hide: TV_TUNER_RESOURCE_MGR_SERVICE,
             //@hide: NETWORK_SCORE_SERVICE,
             USAGE_STATS_SERVICE,
             MEDIA_SESSION_SERVICE,
@@ -4757,6 +4758,17 @@
     public static final String TV_INPUT_SERVICE = "tv_input";
 
     /**
+     * Use with {@link #getSystemService(String)} to retrieve a
+     * {@link android.media.tv.TunerResourceManager} for interacting with TV
+     * tuner resources on the device.
+     *
+     * @see #getSystemService(String)
+     * @see android.media.tv.TunerResourceManager
+     * @hide
+     */
+    public static final String TV_TUNER_RESOURCE_MGR_SERVICE = "tv_tuner_resource_mgr";
+
+    /**
      * {@link android.net.NetworkScoreManager} for managing network scoring.
      * @see #getSystemService(String)
      * @see android.net.NetworkScoreManager
diff --git a/core/java/android/content/integrity/InstallerAllowedByManifestFormula.java b/core/java/android/content/integrity/InstallerAllowedByManifestFormula.java
index 475f019..9d37299 100644
--- a/core/java/android/content/integrity/InstallerAllowedByManifestFormula.java
+++ b/core/java/android/content/integrity/InstallerAllowedByManifestFormula.java
@@ -16,6 +16,10 @@
 
 package android.content.integrity;
 
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
 import java.util.Map;
 
 /**
@@ -25,7 +29,29 @@
  *
  * @hide
  */
-public class InstallerAllowedByManifestFormula extends IntegrityFormula {
+public class InstallerAllowedByManifestFormula extends IntegrityFormula implements Parcelable {
+
+    public static final String INSTALLER_CERTIFICATE_NOT_EVALUATED = "";
+
+    public InstallerAllowedByManifestFormula() {
+    }
+
+    private InstallerAllowedByManifestFormula(Parcel in) {
+    }
+
+    @NonNull
+    public static final Creator<InstallerAllowedByManifestFormula> CREATOR =
+            new Creator<InstallerAllowedByManifestFormula>() {
+                @Override
+                public InstallerAllowedByManifestFormula createFromParcel(Parcel in) {
+                    return new InstallerAllowedByManifestFormula(in);
+                }
+
+                @Override
+                public InstallerAllowedByManifestFormula[] newArray(int size) {
+                    return new InstallerAllowedByManifestFormula[size];
+                }
+            };
 
     @Override
     public int getTag() {
@@ -54,10 +80,30 @@
     private static boolean installerInAllowedInstallersFromManifest(
             AppInstallMetadata appInstallMetadata,
             Map<String, String> allowedInstallersAndCertificates) {
-        return allowedInstallersAndCertificates.containsKey(appInstallMetadata.getInstallerName())
-                && appInstallMetadata.getInstallerCertificates()
-                .contains(
-                        allowedInstallersAndCertificates
-                        .get(appInstallMetadata.getInstallerName()));
+        String installerPackage = appInstallMetadata.getInstallerName();
+
+        if (!allowedInstallersAndCertificates.containsKey(installerPackage)) {
+            return false;
+        }
+
+        // If certificate is not specified in the manifest, we do not check it.
+        if (!allowedInstallersAndCertificates.get(installerPackage)
+                .equals(INSTALLER_CERTIFICATE_NOT_EVALUATED)) {
+            return appInstallMetadata.getInstallerCertificates()
+                    .contains(
+                            allowedInstallersAndCertificates
+                                    .get(appInstallMetadata.getInstallerName()));
+        }
+
+        return true;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
     }
 }
diff --git a/core/java/android/content/integrity/IntegrityFormula.java b/core/java/android/content/integrity/IntegrityFormula.java
index ac4c907..c5e5c8a 100644
--- a/core/java/android/content/integrity/IntegrityFormula.java
+++ b/core/java/android/content/integrity/IntegrityFormula.java
@@ -214,6 +214,8 @@
                 return LongAtomicFormula.CREATOR.createFromParcel(in);
             case BOOLEAN_ATOMIC_FORMULA_TAG:
                 return BooleanAtomicFormula.CREATOR.createFromParcel(in);
+            case INSTALLER_ALLOWED_BY_MANIFEST_FORMULA_TAG:
+                return InstallerAllowedByManifestFormula.CREATOR.createFromParcel(in);
             default:
                 throw new IllegalArgumentException("Unknown formula tag " + tag);
         }
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 471e83c..cb809da 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -62,6 +62,7 @@
 import android.view.ViewDebug;
 import android.view.ViewHierarchyEncoder;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.GrowingArrayUtils;
@@ -112,7 +113,7 @@
     static final String TAG = "Resources";
 
     private static final Object sSync = new Object();
-    private final Object mLock = new Object();
+    private final Object mUpdateLock = new Object();
 
     // Used by BridgeResources in layoutlib
     @UnsupportedAppUsage
@@ -139,6 +140,7 @@
     @UnsupportedAppUsage
     final ClassLoader mClassLoader;
 
+    @GuardedBy("mUpdateLock")
     private UpdateCallbacks mCallbacks = null;
 
     /**
@@ -2375,6 +2377,7 @@
      *
      * <p>Loaders are listed in increasing precedence order. A loader will override the resources
      * and assets of loaders listed before itself.
+     * @hide
      */
     @NonNull
     public List<ResourcesLoader> getLoaders() {
@@ -2382,87 +2385,81 @@
     }
 
     /**
-     * Appends a loader to the end of the loader list. If the loader is already present in the
-     * loader list, the list will not be modified.
-     *
-     * @param loader the loader to add
-     */
-    public void addLoader(@NonNull ResourcesLoader loader) {
-        synchronized (mLock) {
-            checkCallbacksRegistered();
-
-            final List<ResourcesLoader> loaders = new ArrayList<>(
-                    mResourcesImpl.getAssets().getLoaders());
-            if (loaders.contains(loader)) {
-                return;
-            }
-
-            loaders.add(loader);
-            mCallbacks.onLoadersChanged(this, loaders);
-            loader.registerOnProvidersChangedCallback(this, mCallbacks);
-        }
-    }
-
-    /**
-     * Removes a loader from the loaders. If the loader is not present in the loader list, the list
+     * Adds a loader to the list of loaders. If the loader is already present in the list, the list
      * will not be modified.
      *
-     * @param loader the loader to remove
+     * @param loaders the loaders to add
      */
-    public void removeLoader(@NonNull ResourcesLoader loader) {
-        synchronized (mLock) {
+    public void addLoaders(@NonNull ResourcesLoader... loaders) {
+        synchronized (mUpdateLock) {
             checkCallbacksRegistered();
+            final List<ResourcesLoader> newLoaders =
+                    new ArrayList<>(mResourcesImpl.getAssets().getLoaders());
+            final ArraySet<ResourcesLoader> loaderSet = new ArraySet<>(newLoaders);
 
-            final List<ResourcesLoader> loaders = new ArrayList<>(
-                    mResourcesImpl.getAssets().getLoaders());
-            if (!loaders.remove(loader)) {
+            for (int i = 0; i < loaders.length; i++) {
+                final ResourcesLoader loader = loaders[i];
+                if (!loaderSet.contains(loader)) {
+                    newLoaders.add(loader);
+                }
+            }
+
+            if (loaderSet.size() == newLoaders.size()) {
                 return;
             }
 
-            mCallbacks.onLoadersChanged(this, loaders);
-            loader.unregisterOnProvidersChangedCallback(this);
+            mCallbacks.onLoadersChanged(this, newLoaders);
+            for (int i = loaderSet.size(), n = newLoaders.size(); i < n; i++) {
+                newLoaders.get(i).registerOnProvidersChangedCallback(this, mCallbacks);
+            }
         }
     }
 
     /**
-     * Sets the list of loaders.
+     * Removes loaders from the list of loaders. If the loader is not present in the list, the list
+     * will not be modified.
      *
-     * @param loaders the new loaders
+     * @param loaders the loaders to remove
      */
-    public void setLoaders(@NonNull List<ResourcesLoader> loaders) {
-        synchronized (mLock) {
+    public void removeLoaders(@NonNull ResourcesLoader... loaders) {
+        synchronized (mUpdateLock) {
             checkCallbacksRegistered();
-
+            final ArraySet<ResourcesLoader> removedLoaders = new ArraySet<>(loaders);
+            final List<ResourcesLoader> newLoaders = new ArrayList<>();
             final List<ResourcesLoader> oldLoaders = mResourcesImpl.getAssets().getLoaders();
-            int index = 0;
-            boolean modified = loaders.size() != oldLoaders.size();
-            final ArraySet<ResourcesLoader> seenLoaders = new ArraySet<>();
-            for (final ResourcesLoader loader : loaders) {
-                if (!seenLoaders.add(loader)) {
-                    throw new IllegalArgumentException("Loader " + loader + " present twice");
-                }
 
-                if (!modified && oldLoaders.get(index++) != loader) {
-                    modified = true;
+            for (int i = 0, n = oldLoaders.size(); i < n; i++) {
+                final ResourcesLoader loader = oldLoaders.get(i);
+                if (!removedLoaders.contains(loader)) {
+                    newLoaders.add(loader);
                 }
             }
 
-            if (!modified) {
+            if (oldLoaders.size() == newLoaders.size()) {
                 return;
             }
 
-            mCallbacks.onLoadersChanged(this, loaders);
-            for (int i = 0, n = oldLoaders.size(); i < n; i++) {
-                oldLoaders.get(i).unregisterOnProvidersChangedCallback(this);
-            }
-            for (ResourcesLoader newLoader : loaders) {
-                newLoader.registerOnProvidersChangedCallback(this, mCallbacks);
+            mCallbacks.onLoadersChanged(this, newLoaders);
+            for (int i = 0; i < loaders.length; i++) {
+                loaders[i].unregisterOnProvidersChangedCallback(this);
             }
         }
     }
 
-    /** Removes all {@link ResourcesLoader ResourcesLoader(s)}. */
+    /**
+     * Removes all {@link ResourcesLoader ResourcesLoader(s)}.
+     * @hide
+     */
+    @VisibleForTesting
     public void clearLoaders() {
-        setLoaders(Collections.emptyList());
+        synchronized (mUpdateLock) {
+            checkCallbacksRegistered();
+            final List<ResourcesLoader> newLoaders = Collections.emptyList();
+            final List<ResourcesLoader> oldLoaders = mResourcesImpl.getAssets().getLoaders();
+            mCallbacks.onLoadersChanged(this, newLoaders);
+            for (ResourcesLoader loader : oldLoaders) {
+                loader.unregisterOnProvidersChangedCallback(this);
+            }
+        }
     }
 }
diff --git a/core/java/android/content/res/loader/ResourcesLoader.java b/core/java/android/content/res/loader/ResourcesLoader.java
index 69dacee..58fec60 100644
--- a/core/java/android/content/res/loader/ResourcesLoader.java
+++ b/core/java/android/content/res/loader/ResourcesLoader.java
@@ -40,8 +40,8 @@
  * of {@link ResourcesProvider ResourcesProvider(s)} a loader contains propagates to all Resources
  * objects that use the loader.
  *
- * <p>Loaders retrieved with {@link Resources#getLoaders()} are listed in increasing precedence
- * order. A loader will override the resources and assets of loaders listed before itself.
+ * <p>Loaders must be added to Resources objects in increasing precedence order. A loader will
+ * override the resources and assets of loaders added before itself.
  *
  * <p>Providers retrieved with {@link #getProviders()} are listed in increasing precedence order. A
  * provider will override the resources and assets of providers listed before itself.
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 6bda46b..3a0660d 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -699,6 +699,9 @@
                 return context.getString(com.android.internal.R.string.face_error_not_enrolled);
             case FACE_ERROR_HW_NOT_PRESENT:
                 return context.getString(com.android.internal.R.string.face_error_hw_not_present);
+            case BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED:
+                return context.getString(
+                        com.android.internal.R.string.face_error_security_update_required);
             case FACE_ERROR_VENDOR: {
                 String[] msgArray = context.getResources().getStringArray(
                         com.android.internal.R.array.face_error_vendor);
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index ea576bc..f301a5c 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -1011,6 +1011,9 @@
             case FINGERPRINT_ERROR_HW_NOT_PRESENT:
                 return context.getString(
                         com.android.internal.R.string.fingerprint_error_hw_not_present);
+            case BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED:
+                return context.getString(
+                        com.android.internal.R.string.fingerprint_error_security_update_required);
             case FINGERPRINT_ERROR_VENDOR: {
                     String[] msgArray = context.getResources().getStringArray(
                             com.android.internal.R.array.fingerprint_error_vendor);
diff --git a/core/java/android/net/CaptivePortal.java b/core/java/android/net/CaptivePortal.java
index fb35b4b..8afeb30 100644
--- a/core/java/android/net/CaptivePortal.java
+++ b/core/java/android/net/CaptivePortal.java
@@ -15,7 +15,9 @@
  */
 package android.net;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.os.IBinder;
@@ -23,6 +25,8 @@
 import android.os.Parcelable;
 import android.os.RemoteException;
 
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
 /**
  * A class allowing apps handling the {@link ConnectivityManager#ACTION_CAPTIVE_PORTAL_SIGN_IN}
  * activity to indicate to the system different outcomes of captive portal sign in.  This class is
@@ -76,6 +80,17 @@
     private final IBinder mBinder;
 
     /** @hide */
+    @IntDef(value = {
+        MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_ACTIVITY,
+        MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_RESULT_DISMISSED,
+        MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_RESULT_UNWANTED,
+        MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_RESULT_WANTED_AS_IS,
+        MetricsEvent.CAPTIVE_PORTAL_LOGIN_ACTIVITY_SSL_ERROR,
+    })
+    public @interface EventId {
+    }
+
+    /** @hide */
     public CaptivePortal(@NonNull IBinder binder) {
         mBinder = binder;
     }
@@ -153,6 +168,7 @@
      */
     @SystemApi
     @TestApi
+    @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
     public void reevaluateNetwork() {
         try {
             ICaptivePortal.Stub.asInterface(mBinder).appRequest(APP_REQUEST_REEVALUATION_REQUIRED);
@@ -168,7 +184,7 @@
      */
     @SystemApi
     @TestApi
-    public void logEvent(int eventId, @NonNull String packageName) {
+    public void logEvent(@EventId int eventId, @NonNull String packageName) {
         try {
             ICaptivePortal.Stub.asInterface(mBinder).logEvent(eventId, packageName);
         } catch (RemoteException e) {
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
index 09ec6c3..d83715c 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/core/java/android/net/IpSecManager.java
@@ -51,7 +51,7 @@
  *
  * <p>Note that not all aspects of IPsec are permitted by this API. Applications may create
  * transport mode security associations and apply them to individual sockets. Applications looking
- * to create a VPN should use {@link VpnService}.
+ * to create an IPsec VPN should use {@link VpnManager} and {@link Ikev2VpnProfile}.
  *
  * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the
  *     Internet Protocol</a>
diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java
index 6d46c20..3c1b86b 100644
--- a/core/java/android/net/NetworkStatsHistory.java
+++ b/core/java/android/net/NetworkStatsHistory.java
@@ -548,32 +548,32 @@
         final int startIndex = getIndexAfter(end);
         for (int i = startIndex; i >= 0; i--) {
             final long curStart = bucketStart[i];
-            final long curEnd = curStart + bucketDuration;
+            long curEnd = curStart + bucketDuration;
 
             // bucket is older than request; we're finished
             if (curEnd <= start) break;
             // bucket is newer than request; keep looking
             if (curStart >= end) continue;
 
-            // include full value for active buckets, otherwise only fractional
-            final boolean activeBucket = curStart < now && curEnd > now;
-            final long overlap;
-            if (activeBucket) {
-                overlap = bucketDuration;
-            } else {
-                final long overlapEnd = curEnd < end ? curEnd : end;
-                final long overlapStart = curStart > start ? curStart : start;
-                overlap = overlapEnd - overlapStart;
-            }
+            // the active bucket is shorter then a normal completed bucket
+            if (curEnd > now) curEnd = now;
+            // usually this is simply bucketDuration
+            final long bucketSpan = curEnd - curStart;
+            // prevent division by zero
+            if (bucketSpan <= 0) continue;
+
+            final long overlapEnd = curEnd < end ? curEnd : end;
+            final long overlapStart = curStart > start ? curStart : start;
+            final long overlap = overlapEnd - overlapStart;
             if (overlap <= 0) continue;
 
             // integer math each time is faster than floating point
-            if (activeTime != null) entry.activeTime += activeTime[i] * overlap / bucketDuration;
-            if (rxBytes != null) entry.rxBytes += rxBytes[i] * overlap / bucketDuration;
-            if (rxPackets != null) entry.rxPackets += rxPackets[i] * overlap / bucketDuration;
-            if (txBytes != null) entry.txBytes += txBytes[i] * overlap / bucketDuration;
-            if (txPackets != null) entry.txPackets += txPackets[i] * overlap / bucketDuration;
-            if (operations != null) entry.operations += operations[i] * overlap / bucketDuration;
+            if (activeTime != null) entry.activeTime += activeTime[i] * overlap / bucketSpan;
+            if (rxBytes != null) entry.rxBytes += rxBytes[i] * overlap / bucketSpan;
+            if (rxPackets != null) entry.rxPackets += rxPackets[i] * overlap / bucketSpan;
+            if (txBytes != null) entry.txBytes += txBytes[i] * overlap / bucketSpan;
+            if (txPackets != null) entry.txPackets += txPackets[i] * overlap / bucketSpan;
+            if (operations != null) entry.operations += operations[i] * overlap / bucketSpan;
         }
         return entry;
     }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 7c4ec8e..37efec3 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -997,8 +997,9 @@
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you safeguard against this.
      * <p>
-     * Input: Optionally, in versions of Android prior to 11, the Intent's data URI can specify the
-     * application package name to directly invoke the management GUI specific to the package name.
+     * Input: Optionally, in versions of Android prior to {@link android.os.Build.VERSION_CODES#R},
+     * the Intent's data URI can specify the application package name to directly invoke the
+     * management GUI specific to the package name.
      * For example "package:com.my.app".
      * <p>
      * Output: Nothing.
@@ -1011,9 +1012,10 @@
      * Activity Action: Show screen for controlling if the app specified in the data URI of the
      * intent can draw on top of other apps.
      * <p>
-     * Unlike {@link #ACTION_MANAGE_OVERLAY_PERMISSION}, which in Android 11 can't be used to show
-     * a GUI for a specific package, permission {@code android.permission.INTERNAL_SYSTEM_WINDOW} is
-     * needed to start an activity with this intent.
+     * Unlike {@link #ACTION_MANAGE_OVERLAY_PERMISSION}, which in Android {@link
+     * android.os.Build.VERSION_CODES#R} can't be used to show a GUI for a specific package,
+     * permission {@code android.permission.INTERNAL_SYSTEM_WINDOW} is needed to start an activity
+     * with this intent.
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you
      * safeguard against this.
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index e65bd9f..d273500 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -301,6 +301,13 @@
     public static final int LISTEN_USER_MOBILE_DATA_STATE                  = 0x00080000;
 
     /**
+     *  Listen for display info changed event.
+     *
+     *  @see #onDisplayInfoChanged
+     */
+    public static final int LISTEN_DISPLAY_INFO_CHANGED = 0x00100000;
+
+    /**
      *  Listen for changes to the phone capability.
      *
      *  @see #onPhoneCapabilityChanged
@@ -848,6 +855,21 @@
     }
 
     /**
+     * Callback invoked when the display info has changed on the registered subscription.
+     * <p> The {@link DisplayInfo} contains status information shown to the user based on
+     * carrier policy.
+     *
+     * Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE} or that the calling
+     * app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}).
+     *
+     * @param displayInfo The display information.
+     */
+    @RequiresPermission((android.Manifest.permission.READ_PHONE_STATE))
+    public void onDisplayInfoChanged(@NonNull DisplayInfo displayInfo) {
+        // default implementation empty
+    }
+
+    /**
      * Callback invoked when the current emergency number list has changed on the registered
      * subscription.
      * Note, the registration subId comes from {@link TelephonyManager} object which registers
@@ -1226,6 +1248,15 @@
                             () -> psl.onUserMobileDataStateChanged(enabled)));
         }
 
+        public void onDisplayInfoChanged(DisplayInfo displayInfo) {
+            PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+            if (psl == null) return;
+
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(
+                            () -> psl.onDisplayInfoChanged(displayInfo)));
+        }
+
         public void onOemHookRawEvent(byte[] rawData) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 4024db1..2c077bb 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -589,6 +589,24 @@
     }
 
     /**
+     * Notify display info changed.
+     *
+     * @param slotIndex The SIM slot index for which display info has changed. Can be
+     * derived from {@code subscriptionId} except when {@code subscriptionId} is invalid, such as
+     * when the device is in emergency-only mode.
+     * @param subscriptionId Subscription id for which display network info has changed.
+     * @param displayInfo The display info.
+     */
+    public void notifyDisplayInfoChanged(int slotIndex, int subscriptionId,
+                                         @NonNull DisplayInfo displayInfo) {
+        try {
+            sRegistry.notifyDisplayInfoChanged(slotIndex, subscriptionId, displayInfo);
+        } catch (RemoteException ex) {
+            // system process is dead
+        }
+    }
+
+    /**
      * Notify IMS call disconnect causes which contains {@link android.telephony.ims.ImsReasonInfo}.
      *
      * @param subId for which ims call disconnect.
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 2548068..9cd6050e 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -882,6 +882,9 @@
         } else {
             hideDirectly(types);
         }
+        if (mViewRoot.mView == null) {
+            return;
+        }
         mViewRoot.mView.dispatchWindowInsetsAnimationPrepare(animation);
         mViewRoot.mView.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
             @Override
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 5964e12..857bc50 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -223,6 +223,13 @@
      * @see #USE_NEW_INSETS_PROPERTY
      * @hide
      */
+    public static int sNewInsetsMode =
+            SystemProperties.getInt(USE_NEW_INSETS_PROPERTY, 0);
+
+    /**
+     * @see #USE_NEW_INSETS_PROPERTY
+     * @hide
+     */
     public static final int NEW_INSETS_MODE_NONE = 0;
 
     /**
@@ -238,13 +245,6 @@
     public static final int NEW_INSETS_MODE_FULL = 2;
 
     /**
-     * @see #USE_NEW_INSETS_PROPERTY
-     * @hide
-     */
-    public static int sNewInsetsMode =
-            SystemProperties.getInt(USE_NEW_INSETS_PROPERTY, NEW_INSETS_MODE_IME);
-
-    /**
      * Set this system property to true to force the view hierarchy to render
      * at 60 Hz. This can be used to measure the potential framerate.
      */
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index 93659a4..a1c22e9 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -22,6 +22,7 @@
 import static com.android.internal.util.ArrayUtils.convertToLongArray;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.annotation.IntDef;
 import android.app.ActivityManager;
 import android.app.ActivityThread;
 import android.app.AlertDialog;
@@ -53,6 +54,8 @@
 import com.android.internal.R;
 import com.android.internal.util.function.pooled.PooledLambda;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
@@ -85,6 +88,17 @@
     private boolean mEnabledOnLockScreen;
     private int mUserId;
 
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            DialogStaus.NOT_SHOWN,
+            DialogStaus.SHOWN,
+    })
+    /** Denotes the user shortcut type. */
+    private @interface DialogStaus {
+        int NOT_SHOWN = 0;
+        int SHOWN  = 1;
+    }
+
     // Visible for testing
     public FrameworkObjectProvider mFrameworkObjectProvider = new FrameworkObjectProvider();
 
@@ -163,7 +177,8 @@
                 cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED, 1, mUserId) == 1;
         // Enable the shortcut from the lockscreen by default if the dialog has been shown
         final int dialogAlreadyShown = Settings.Secure.getIntForUser(
-                cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0, mUserId);
+                cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, DialogStaus.NOT_SHOWN,
+                mUserId);
         mEnabledOnLockScreen = Settings.Secure.getIntForUser(
                 cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN,
                 dialogAlreadyShown, mUserId) == 1;
@@ -178,7 +193,8 @@
         final ContentResolver cr = mContext.getContentResolver();
         final int userId = ActivityManager.getCurrentUser();
         final int dialogAlreadyShown = Settings.Secure.getIntForUser(
-                cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0, userId);
+                cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, DialogStaus.NOT_SHOWN,
+                userId);
         // Play a notification vibration
         Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
         if ((vibrator != null) && vibrator.hasVibrator()) {
@@ -205,7 +221,8 @@
             w.setAttributes(attr);
             mAlertDialog.show();
             Settings.Secure.putIntForUser(
-                    cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1, userId);
+                    cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, DialogStaus.SHOWN,
+                    userId);
         } else {
             playNotificationTone();
             if (mAlertDialog != null) {
@@ -251,15 +268,8 @@
     }
 
     private AlertDialog createShortcutWarningDialog(int userId) {
-        final String serviceDescription = getShortcutFeatureDescription(true /* Include summary */);
-
-        if (serviceDescription == null) {
-            return null;
-        }
-
-        final String warningMessage = String.format(
-                mContext.getString(R.string.accessibility_shortcut_toogle_warning),
-                serviceDescription);
+        final String warningMessage = mContext.getString(
+                R.string.accessibility_shortcut_toogle_warning);
         final AlertDialog alertDialog = mFrameworkObjectProvider.getAlertDialogBuilder(
                 // Use SystemUI context so we pick up any theme set in a vendor overlay
                 mFrameworkObjectProvider.getSystemUiContext())
@@ -272,11 +282,17 @@
                             Settings.Secure.putStringForUser(mContext.getContentResolver(),
                                     Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "",
                                     userId);
+
+                            // If canceled, treat as if the dialog has never been shown
+                            Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                                    Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+                                    DialogStaus.NOT_SHOWN, userId);
                         })
                 .setOnCancelListener((DialogInterface d) -> {
                     // If canceled, treat as if the dialog has never been shown
                     Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                        Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0, userId);
+                            Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+                            DialogStaus.NOT_SHOWN, userId);
                 })
                 .create();
         return alertDialog;
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 022573c..c2c9fff 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1278,10 +1278,9 @@
             throw new IllegalStateException("mMultiProfilePagerAdapter.getCurrentListAdapter() "
                     + "cannot be null.");
         }
-        boolean rebuildCompleted = mMultiProfilePagerAdapter.rebuildActiveTab(true);
-
         // We partially rebuild the inactive adapter to determine if we should auto launch
-        mMultiProfilePagerAdapter.rebuildInactiveTab(false);
+        boolean rebuildActiveCompleted = mMultiProfilePagerAdapter.rebuildActiveTab(true);
+        boolean rebuildInactiveCompleted = mMultiProfilePagerAdapter.rebuildInactiveTab(false);
 
         if (useLayoutWithDefault()) {
             mLayoutId = R.layout.resolver_list_with_default;
@@ -1290,7 +1289,7 @@
         }
         setContentView(mLayoutId);
         mMultiProfilePagerAdapter.setupViewPager(findViewById(R.id.profile_pager));
-        return postRebuildList(rebuildCompleted);
+        return postRebuildList(rebuildActiveCompleted && rebuildInactiveCompleted);
     }
 
     /**
@@ -1338,10 +1337,11 @@
         int numberOfProfiles = mMultiProfilePagerAdapter.getItemCount();
         if (numberOfProfiles == 1 && maybeAutolaunchIfSingleTarget()) {
             return true;
-        } else if (numberOfProfiles == 2 && maybeAutolaunchIfCrossProfileSupported()) {
-            // note that autolaunching when we have 2 profiles, 1 resolved target on the active
-            // tab and 0 resolved targets on the inactive tab, is already handled before launching
-            // ResolverActivity
+        } else if (numberOfProfiles == 2
+                && mMultiProfilePagerAdapter.getActiveListAdapter().isListLoaded()
+                && mMultiProfilePagerAdapter.getInactiveListAdapter().isListLoaded()
+                && (maybeAutolaunchIfNoAppsOnInactiveTab()
+                        || maybeAutolaunchIfCrossProfileSupported())) {
             return true;
         }
         return false;
@@ -1364,6 +1364,23 @@
         return false;
     }
 
+    private boolean maybeAutolaunchIfNoAppsOnInactiveTab() {
+        int count = mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredCount();
+        if (count != 1) {
+            return false;
+        }
+        ResolverListAdapter inactiveListAdapter =
+                mMultiProfilePagerAdapter.getInactiveListAdapter();
+        if (inactiveListAdapter.getUnfilteredCount() != 0) {
+            return false;
+        }
+        TargetInfo target = mMultiProfilePagerAdapter.getActiveListAdapter()
+                .targetInfoForPosition(0, false);
+        safelyStartActivity(target);
+        finish();
+        return true;
+    }
+
     /**
      * When we have a personal and a work profile, we auto launch in the following scenario:
      * - There is 1 resolved target on each profile
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
index ea84090..54453d0 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -87,6 +87,7 @@
     private final ResolverListCommunicator mResolverListCommunicator;
     private Runnable mPostListReadyRunnable;
     private final boolean mIsAudioCaptureDevice;
+    private boolean mIsListLoaded;
 
     public ResolverListAdapter(Context context, List<Intent> payloadIntents,
             Intent[] initialIntents, List<ResolveInfo> rList,
@@ -191,6 +192,7 @@
         mLastChosenPosition = -1;
         mAllTargetsAreBrowsers = false;
         mDisplayList.clear();
+        mIsListLoaded = false;
 
         if (mBaseResolveList != null) {
             currentResolveList = mUnfilteredResolveList = new ArrayList<>();
@@ -352,6 +354,7 @@
 
         mResolverListCommunicator.sendVoiceChoicesIfNeeded();
         postListReadyRunnable(doPostProcessing);
+        mIsListLoaded = true;
     }
 
     /**
@@ -611,6 +614,10 @@
         return mIntents;
     }
 
+    protected boolean isListLoaded() {
+        return mIsListLoaded;
+    }
+
     /**
      * Necessary methods to communicate between {@link ResolverListAdapter}
      * and {@link ResolverActivity}.
diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
index 0f50596..3d5dfbb 100644
--- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -21,6 +21,7 @@
 import android.telephony.CellIdentity;
 import android.telephony.CellInfo;
 import android.telephony.DataConnectionRealTimeInfo;
+import android.telephony.DisplayInfo;
 import android.telephony.PhoneCapability;
 import android.telephony.PreciseCallState;
 import android.telephony.PreciseDataConnectionState;
@@ -54,6 +55,7 @@
     void onOemHookRawEvent(in byte[] rawData);
     void onCarrierNetworkChange(in boolean active);
     void onUserMobileDataStateChanged(in boolean enabled);
+    void onDisplayInfoChanged(in DisplayInfo displayInfo);
     void onPhoneCapabilityChanged(in PhoneCapability capability);
     void onActiveDataSubIdChanged(in int subId);
     void onRadioPowerStateChanged(in int state);
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 47752c5..520ffc9 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -23,6 +23,7 @@
 import android.telephony.CallQuality;
 import android.telephony.CellIdentity;
 import android.telephony.CellInfo;
+import android.telephony.DisplayInfo;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.PhoneCapability;
 import android.telephony.PhysicalChannelConfig;
@@ -87,6 +88,7 @@
     void notifyOpportunisticSubscriptionInfoChanged();
     void notifyCarrierNetworkChange(in boolean active);
     void notifyUserMobileDataStateChangedForPhoneId(in int phoneId, in int subId, in boolean state);
+    void notifyDisplayInfoChanged(int slotIndex, int subId, in DisplayInfo displayInfo);
     void notifyPhoneCapabilityChanged(in PhoneCapability capability);
     void notifyActiveDataSubIdChanged(int activeDataSubId);
     void notifyRadioPowerStateChanged(in int phoneId, in int subId, in int state);
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index b47b7e3..0b1c8b7 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -182,6 +182,8 @@
 extern int register_android_view_MotionEvent(JNIEnv* env);
 extern int register_android_view_PointerIcon(JNIEnv* env);
 extern int register_android_view_VelocityTracker(JNIEnv* env);
+extern int register_android_view_VerifiedKeyEvent(JNIEnv* env);
+extern int register_android_view_VerifiedMotionEvent(JNIEnv* env);
 extern int register_android_content_res_ObbScanner(JNIEnv* env);
 extern int register_android_content_res_Configuration(JNIEnv* env);
 extern int register_android_animation_PropertyValuesHolder(JNIEnv *env);
@@ -1562,6 +1564,8 @@
         REG_JNI(register_android_view_MotionEvent),
         REG_JNI(register_android_view_PointerIcon),
         REG_JNI(register_android_view_VelocityTracker),
+        REG_JNI(register_android_view_VerifiedKeyEvent),
+        REG_JNI(register_android_view_VerifiedMotionEvent),
 
         REG_JNI(register_android_content_res_ObbScanner),
         REG_JNI(register_android_content_res_Configuration),
diff --git a/core/jni/android_view_VerifiedKeyEvent.cpp b/core/jni/android_view_VerifiedKeyEvent.cpp
index 8fc301c..bba10aa 100644
--- a/core/jni/android_view_VerifiedKeyEvent.cpp
+++ b/core/jni/android_view_VerifiedKeyEvent.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "MotionEvent-JNI"
+#define LOG_TAG "VerifiedKey-JNI"
 
 #include "android_view_VerifiedKeyEvent.h"
 #include <input/Input.h>
@@ -22,18 +22,29 @@
 
 namespace android {
 
+static struct {
+    jclass clazz;
+
+    jmethodID constructor;
+} gVerifiedKeyEventClassInfo;
+
 // ----------------------------------------------------------------------------
 
 jobject android_view_VerifiedKeyEvent(JNIEnv* env, const VerifiedKeyEvent& event) {
-    static jclass clazz = FindClassOrDie(env, "android/view/VerifiedKeyEvent");
+    return env->NewObject(gVerifiedKeyEventClassInfo.clazz, gVerifiedKeyEventClassInfo.constructor,
+                          event.deviceId, event.eventTimeNanos, event.source, event.displayId,
+                          event.action, event.downTimeNanos, event.flags, event.keyCode,
+                          event.scanCode, event.metaState, event.repeatCount);
+}
 
-    static jmethodID constructor = GetMethodIDOrDie(env, clazz, "<init>", "(IJIIIJIIIII)V");
+int register_android_view_VerifiedKeyEvent(JNIEnv* env) {
+    jclass clazz = FindClassOrDie(env, "android/view/VerifiedKeyEvent");
+    gVerifiedKeyEventClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
 
-    jobject object =
-            env->NewObject(clazz, constructor, event.deviceId, event.eventTimeNanos, event.source,
-                           event.displayId, event.action, event.downTimeNanos, event.flags,
-                           event.keyCode, event.scanCode, event.metaState, event.repeatCount);
-    return object;
+    gVerifiedKeyEventClassInfo.constructor =
+            GetMethodIDOrDie(env, clazz, "<init>", "(IJIIIJIIIII)V");
+
+    return OK;
 }
 
 } // namespace android
diff --git a/core/jni/android_view_VerifiedMotionEvent.cpp b/core/jni/android_view_VerifiedMotionEvent.cpp
index 7a5c71a..c281197 100644
--- a/core/jni/android_view_VerifiedMotionEvent.cpp
+++ b/core/jni/android_view_VerifiedMotionEvent.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "MotionEvent-JNI"
+#define LOG_TAG "VerifiedMotion-JNI"
 
 #include "android_view_VerifiedMotionEvent.h"
 #include <input/Input.h>
@@ -22,18 +22,30 @@
 
 namespace android {
 
+static struct {
+    jclass clazz;
+
+    jmethodID constructor;
+} gVerifiedMotionEventClassInfo;
+
 // ----------------------------------------------------------------------------
 
 jobject android_view_VerifiedMotionEvent(JNIEnv* env, const VerifiedMotionEvent& event) {
-    static jclass clazz = FindClassOrDie(env, "android/view/VerifiedMotionEvent");
+    return env->NewObject(gVerifiedMotionEventClassInfo.clazz,
+                          gVerifiedMotionEventClassInfo.constructor, event.deviceId,
+                          event.eventTimeNanos, event.source, event.displayId, event.rawX,
+                          event.rawY, event.actionMasked, event.downTimeNanos, event.flags,
+                          event.metaState, event.buttonState);
+}
 
-    static jmethodID constructor = GetMethodIDOrDie(env, clazz, "<init>", "(IJIIFFIJIII)V");
+int register_android_view_VerifiedMotionEvent(JNIEnv* env) {
+    jclass clazz = FindClassOrDie(env, "android/view/VerifiedMotionEvent");
+    gVerifiedMotionEventClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
 
-    jobject object =
-            env->NewObject(clazz, constructor, event.deviceId, event.eventTimeNanos, event.source,
-                           event.displayId, event.rawX, event.rawY, event.actionMasked,
-                           event.downTimeNanos, event.flags, event.metaState, event.buttonState);
-    return object;
+    gVerifiedMotionEventClassInfo.constructor =
+            GetMethodIDOrDie(env, clazz, "<init>", "(IJIIFFIJIII)V");
+
+    return OK;
 }
 
 } // namespace android
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index bf4cdee..03676dd 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -428,7 +428,7 @@
         (section).args = "dropbox --proto system_app_wtf"
     ];
 
-    optional android.service.dropbox.DropBoxManagerServiceDumpProto dropbox_system_server_crashes = 3037 [
+    optional android.service.dropbox.DropBoxManagerServiceDumpProto dropbox_system_server_crash = 3037 [
         (section).type = SECTION_DUMPSYS,
         (section).args = "dropbox --proto system_server_crash"
     ];
diff --git a/core/proto/android/stats/devicepolicy/device_policy_enums.proto b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
index d1392a5..a648831 100644
--- a/core/proto/android/stats/devicepolicy/device_policy_enums.proto
+++ b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
@@ -159,4 +159,7 @@
   ALLOW_MODIFICATION_OF_ADMIN_CONFIGURED_NETWORKS = 132;
   SET_TIME = 133;
   SET_TIME_ZONE = 134;
+  SET_PERSONAL_APPS_SUSPENDED = 135;
+  SET_MANAGED_PROFILE_MAXIMUM_TIME_OFF = 136;
+  COMP_TO_ORG_OWNED_PO_MIGRATED = 137;
 }
diff --git a/core/proto/android/stats/mediametrics/mediametrics.proto b/core/proto/android/stats/mediametrics/mediametrics.proto
index 34ed90a..e1af962 100644
--- a/core/proto/android/stats/mediametrics/mediametrics.proto
+++ b/core/proto/android/stats/mediametrics/mediametrics.proto
@@ -154,6 +154,8 @@
     optional int64 latency_avg = 18;
     optional int64 latency_count = 19;
     optional int64 latency_unknown = 20;
+    optional int32 queue_input_buffer_error = 21;
+    optional int32 queue_secure_input_buffer_error = 22;
 }
 
 /**
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index d1031f4..60b3a19 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1640,7 +1640,7 @@
 
     <!-- Allows network stack services (Connectivity and Wifi) to coordinate
          <p>Not for use by third-party or privileged applications.
-         @SystemApi
+         @SystemApi @TestApi
          @hide This should only be used by Connectivity and Wifi Services.
     -->
     <permission android:name="android.permission.NETWORK_STACK"
@@ -4943,6 +4943,10 @@
     <permission android:name="android.permission.ACCESS_TV_TUNER"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @hide @SystemApi Allows an application to access locusId events in the usage stats. -->
+    <permission android:name="android.permission.ACCESS_LOCUS_ID_USAGE_STATS"
+                android:protectionLevel="signature|appPredictor" />
+
     <application android:process="system"
                  android:persistent="true"
                  android:hasCode="false"
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 39cd00c..e6a93e5 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1483,6 +1483,8 @@
     <string name="fingerprint_error_no_fingerprints">No fingerprints enrolled.</string>
     <!-- Generic error message shown when the app requests fingerprint authentication on a device without a sensor -->
     <string name="fingerprint_error_hw_not_present">This device does not have a fingerprint sensor.</string>
+    <!-- Generic error message shown when fingerprint is not available due to a security vulnerability. [CHAR LIMIT=50] -->
+    <string name="fingerprint_error_security_update_required">Sensor temporarily disabled.</string>
 
     <!-- Template to be used to name enrolled fingerprints by default. -->
     <string name="fingerprint_name_template">Finger <xliff:g id="fingerId" example="1">%d</xliff:g></string>
@@ -1574,6 +1576,8 @@
     <string name="face_error_not_enrolled">You haven\u2019t set up face unlock.</string>
     <!-- Generic error message shown when the app requests face unlock on a device without a sensor. [CHAR LIMIT=61] -->
     <string name="face_error_hw_not_present">Face unlock is not supported on this device.</string>
+    <!-- Generic error message shown when face unlock is not available due to a security vulnerability. [CHAR LIMIT=50] -->
+    <string name="face_error_security_update_required">Sensor temporarily disabled.</string>
 
     <!-- Template to be used to name enrolled faces by default. [CHAR LIMIT=10] -->
     <string name="face_name_template">Face <xliff:g id="faceId" example="1">%d</xliff:g></string>
@@ -4372,10 +4376,7 @@
     service via the volume buttons shortcut for the first time. [CHAR LIMIT=none] -->
     <string name="accessibility_shortcut_toogle_warning">
         When the shortcut is on, pressing both volume buttons for 3 seconds will start an
-        accessibility feature.\n\n
-        Current accessibility feature:\n
-        <xliff:g id="service_name" example="TalkBack">%1$s</xliff:g>\n\n
-        You can change the feature in Settings > Accessibility.
+        accessibility feature.
     </string>
 
     <!-- Text in button that edit the accessibility shortcut menu, user can delete
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 8581084..5aefe11 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2458,6 +2458,7 @@
   <java-symbol type="string" name="fingerprint_authenticated" />
   <java-symbol type="string" name="fingerprint_error_no_fingerprints" />
   <java-symbol type="string" name="fingerprint_error_hw_not_present" />
+  <java-symbol type="string" name="fingerprint_error_security_update_required" />
 
   <!-- Fingerprint config -->
   <java-symbol type="integer" name="config_fingerprintMaxTemplatesPerUser"/>
@@ -2502,6 +2503,7 @@
   <java-symbol type="string" name="face_name_template" />
   <java-symbol type="string" name="face_authenticated_no_confirmation_required" />
   <java-symbol type="string" name="face_authenticated_confirmation_required" />
+  <java-symbol type="string" name="face_error_security_update_required" />
 
   <java-symbol type="array" name="config_biometric_sensors" />
 
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryAssetsProviderTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryAssetsProviderTest.kt
index 9e94bdc..afe9d7f 100644
--- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryAssetsProviderTest.kt
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryAssetsProviderTest.kt
@@ -44,7 +44,7 @@
         testDir = context.filesDir.resolve("DirectoryAssetsProvider_${testName.methodName}")
         assetsProvider = DirectoryAssetsProvider(testDir)
         loader = ResourcesLoader()
-        resources.addLoader(loader)
+        resources.addLoaders(loader)
     }
 
     @After
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetsTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetsTest.kt
index e3ba93d..da5092d 100644
--- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetsTest.kt
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetsTest.kt
@@ -119,7 +119,7 @@
 
         val loader = ResourcesLoader()
         loader.providers = listOf(one, two)
-        resources.addLoader(loader)
+        resources.addLoaders(loader)
 
         assertOpenedAsset()
         inOrder(two.assetsProvider, one.assetsProvider).apply {
@@ -149,7 +149,7 @@
         val loader2 = ResourcesLoader()
         loader2.addProvider(two)
 
-        resources.loaders = listOf(loader1, loader2)
+        resources.addLoaders(loader1, loader2)
 
         assertOpenedAsset()
         inOrder(two.assetsProvider, one.assetsProvider).apply {
@@ -170,7 +170,7 @@
         val loader = ResourcesLoader()
         val one = ResourcesProvider.empty(assetsProvider1)
         val two = ResourcesProvider.empty(assetsProvider2)
-        resources.addLoader(loader)
+        resources.addLoaders(loader)
         loader.providers = listOf(one, two)
 
         assertOpenedAsset()
@@ -186,7 +186,7 @@
         val loader = ResourcesLoader()
         val one = ResourcesProvider.empty(assetsProvider1)
         val two = ResourcesProvider.empty(assetsProvider2)
-        resources.addLoader(loader)
+        resources.addLoaders(loader)
         loader.providers = listOf(one, two)
 
         assertOpenedAsset()
@@ -202,7 +202,7 @@
         val loader = ResourcesLoader()
         val one = ResourcesProvider.empty(assetsProvider1)
         val two = ResourcesProvider.empty(assetsProvider2)
-        resources.addLoader(loader)
+        resources.addLoaders(loader)
         loader.providers = listOf(one, two)
 
         assertOpenedAsset()
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt
index 0cc56d7..16eafcd 100644
--- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt
@@ -192,13 +192,13 @@
     }
 
     @Test
-    fun addMultipleProviders() {
+    fun addProvidersRepeatedly() {
         val originalValue = getValue()
         val testOne = openOne()
         val testTwo = openTwo()
         val loader = ResourcesLoader()
 
-        resources.addLoader(loader)
+        resources.addLoaders(loader)
         loader.addProvider(testOne)
         assertEquals(valueOne, getValue())
 
@@ -213,25 +213,25 @@
     }
 
     @Test
-    fun addMultipleLoaders() {
+    fun addLoadersRepeatedly() {
         val originalValue = getValue()
         val testOne = openOne()
         val testTwo = openTwo()
         val loader1 = ResourcesLoader()
         val loader2 = ResourcesLoader()
 
-        resources.addLoader(loader1)
+        resources.addLoaders(loader1)
         loader1.addProvider(testOne)
         assertEquals(valueOne, getValue())
 
-        resources.addLoader(loader2)
+        resources.addLoaders(loader2)
         loader2.addProvider(testTwo)
         assertEquals(valueTwo, getValue())
 
-        resources.removeLoader(loader1)
+        resources.removeLoaders(loader1)
         assertEquals(valueTwo, getValue())
 
-        resources.removeLoader(loader2)
+        resources.removeLoaders(loader2)
         assertEquals(originalValue, getValue())
     }
 
@@ -242,7 +242,7 @@
         val testTwo = openTwo()
         val loader = ResourcesLoader()
 
-        resources.addLoader(loader)
+        resources.addLoaders(loader)
         loader.providers = listOf(testOne, testTwo)
         assertEquals(valueTwo, getValue())
 
@@ -254,20 +254,20 @@
     }
 
     @Test
-    fun setMultipleLoaders() {
+    fun addMultipleLoaders() {
         val originalValue = getValue()
         val loader1 = ResourcesLoader()
         loader1.addProvider(openOne())
         val loader2 = ResourcesLoader()
         loader2.addProvider(openTwo())
 
-        resources.loaders = listOf(loader1, loader2)
+        resources.addLoaders(loader1, loader2)
         assertEquals(valueTwo, getValue())
 
-        resources.removeLoader(loader2)
+        resources.removeLoaders(loader2)
         assertEquals(valueOne, getValue())
 
-        resources.loaders = Collections.emptyList()
+        resources.removeLoaders(loader1)
         assertEquals(originalValue, getValue())
     }
 
@@ -291,7 +291,7 @@
         val testTwo = openTwo()
         val loader = ResourcesLoader()
 
-        resources.addLoader(loader)
+        resources.addLoaders(loader)
         loader.addProvider(testOne)
         loader.addProvider(testTwo)
         loader.addProvider(testOne)
@@ -308,9 +308,9 @@
         val loader2 = ResourcesLoader()
         loader2.addProvider(openTwo())
 
-        resources.addLoader(loader1)
-        resources.addLoader(loader2)
-        resources.addLoader(loader1)
+        resources.addLoaders(loader1)
+        resources.addLoaders(loader2)
+        resources.addLoaders(loader1)
 
         assertEquals(2, resources.loaders.size)
         assertEquals(resources.loaders[0], loader1)
@@ -323,7 +323,7 @@
         val testTwo = openTwo()
         val loader = ResourcesLoader()
 
-        resources.addLoader(loader)
+        resources.addLoaders(loader)
         loader.addProvider(testOne)
         loader.addProvider(testTwo)
 
@@ -341,12 +341,16 @@
         val loader2 = ResourcesLoader()
         loader2.addProvider(openTwo())
 
-        resources.loaders = listOf(loader1, loader2)
-        resources.removeLoader(loader1)
-        resources.removeLoader(loader1)
+        resources.addLoaders(loader1, loader2)
+        resources.removeLoaders(loader1)
+        resources.removeLoaders(loader1)
 
         assertEquals(1, resources.loaders.size)
         assertEquals(resources.loaders[0], loader2)
+
+        resources.removeLoaders(loader2, loader2)
+
+        assertEquals(0, resources.loaders.size)
     }
 
     @Test
@@ -355,7 +359,7 @@
         val testTwo = openTwo()
         val loader = ResourcesLoader()
 
-        resources.addLoader(loader)
+        resources.addLoaders(loader)
         loader.providers = listOf(testOne, testTwo)
         loader.providers = listOf(testOne, testTwo)
 
@@ -365,14 +369,14 @@
     }
 
     @Test
-    fun repeatedSetLoaders() {
+    fun repeatedAddMultipleLoaders() {
         val loader1 = ResourcesLoader()
         loader1.addProvider(openOne())
         val loader2 = ResourcesLoader()
         loader2.addProvider(openTwo())
 
-        resources.loaders = listOf(loader1, loader2)
-        resources.loaders = listOf(loader1, loader2)
+        resources.addLoaders(loader1, loader2)
+        resources.addLoaders(loader1, loader2)
 
         assertEquals(2, resources.loaders.size)
         assertEquals(resources.loaders[0], loader1)
@@ -386,7 +390,7 @@
         val testTwo = openTwo()
         val loader = ResourcesLoader()
 
-        resources.addLoader(loader)
+        resources.addLoaders(loader)
         loader.addProvider(testOne)
         loader.addProvider(testTwo)
         assertEquals(valueTwo, getValue())
@@ -414,20 +418,20 @@
         val loader2 = ResourcesLoader()
         loader2.addProvider(testTwo)
 
-        resources.addLoader(loader1)
-        resources.addLoader(loader2)
+        resources.addLoaders(loader1)
+        resources.addLoaders(loader2)
         assertEquals(valueTwo, getValue())
 
-        resources.removeLoader(loader1)
+        resources.removeLoaders(loader1)
         assertEquals(valueTwo, getValue())
 
-        resources.addLoader(loader1)
+        resources.addLoaders(loader1)
         assertEquals(valueOne, getValue())
 
-        resources.removeLoader(loader2)
+        resources.removeLoaders(loader2)
         assertEquals(valueOne, getValue())
 
-        resources.removeLoader(loader1)
+        resources.removeLoaders(loader1)
         assertEquals(originalValue, getValue())
     }
 
@@ -444,10 +448,11 @@
         val loader2 = ResourcesLoader()
         loader2.providers = listOf(testThree, testFour)
 
-        resources.loaders = listOf(loader1, loader2)
+        resources.addLoaders(loader1, loader2)
         assertEquals(valueFour, getValue())
 
-        resources.loaders = listOf(loader2, loader1)
+        resources.removeLoaders(loader1)
+        resources.addLoaders(loader1)
         assertEquals(valueTwo, getValue())
 
         loader1.removeProvider(testTwo)
@@ -471,7 +476,7 @@
         val loader2 = ResourcesLoader()
         loader2.addProvider(openTwo())
 
-        resources.loaders = listOf(loader1)
+        resources.addLoaders(loader1)
         assertEquals(valueOne, getValue())
 
         // The child context should include the loaders of the original context.
@@ -479,12 +484,12 @@
         assertEquals(valueOne, getValue(childContext))
 
         // Changing the loaders of the child context should not affect the original context.
-        childContext.resources.loaders = listOf(loader1, loader2)
+        childContext.resources.addLoaders(loader2)
         assertEquals(valueOne, getValue())
         assertEquals(valueTwo, getValue(childContext))
 
         // Changing the loaders of the original context should not affect the child context.
-        resources.removeLoader(loader1)
+        resources.removeLoaders(loader1)
         assertEquals(originalValue, getValue())
         assertEquals(valueTwo, getValue(childContext))
 
@@ -506,7 +511,7 @@
         val testTwo = openTwo()
         val loader = ResourcesLoader()
 
-        resources.addLoader(loader)
+        resources.addLoaders(loader)
         loader.addProvider(testOne)
         assertEquals(valueOne, getValue())
 
@@ -527,7 +532,7 @@
         assertEquals(originalValue, getValue())
         assertEquals(originalValue, getValue(childContext2))
 
-        childContext2.resources.addLoader(loader)
+        childContext2.resources.addLoaders(loader)
         assertEquals(originalValue, getValue())
         assertEquals(valueTwo, getValue(childContext))
         assertEquals(valueTwo, getValue(childContext2))
@@ -539,7 +544,7 @@
         loader.addProvider(openOne())
 
         val applicationContext = context.applicationContext
-        applicationContext.resources.addLoader(loader)
+        applicationContext.resources.addLoaders(loader)
         assertEquals(valueOne, getValue(applicationContext))
 
         val activity = mTestActivityRule.launchActivity(Intent())
@@ -556,7 +561,7 @@
         loader2.addProvider(openTwo())
 
         val applicationContext = context.applicationContext
-        applicationContext.resources.addLoader(loader1)
+        applicationContext.resources.addLoaders(loader1)
         assertEquals(valueOne, getValue(applicationContext))
 
         var token: IBinder? = null
@@ -569,7 +574,7 @@
             assertEquals(valueOne, getValue(applicationContext))
             assertEquals(valueOne, getValue(activity))
 
-            activity.resources.addLoader(loader2)
+            activity.resources.addLoaders(loader2)
             assertEquals(valueOne, getValue(applicationContext))
             assertEquals(valueTwo, getValue(activity))
 
@@ -598,10 +603,11 @@
         loader2.addProvider(provider1)
         loader2.addProvider(openTwo())
 
-        resources.loaders = listOf(loader1, loader2)
+        resources.addLoaders(loader1, loader2)
         assertEquals(valueTwo, getValue())
 
-        resources.loaders = listOf(loader2, loader1)
+        resources.removeLoaders(loader1)
+        resources.addLoaders(loader1)
         assertEquals(valueOne, getValue())
 
         assertEquals(2, resources.assets.apkAssets.count { apkAssets -> apkAssets.isForLoader })
diff --git a/core/tests/coretests/src/android/content/integrity/InstallerAllowedByManifestFormulaTest.java b/core/tests/coretests/src/android/content/integrity/InstallerAllowedByManifestFormulaTest.java
index c897ace..693d4ca 100644
--- a/core/tests/coretests/src/android/content/integrity/InstallerAllowedByManifestFormulaTest.java
+++ b/core/tests/coretests/src/android/content/integrity/InstallerAllowedByManifestFormulaTest.java
@@ -16,15 +16,20 @@
 
 package android.content.integrity;
 
+import static android.content.integrity.InstallerAllowedByManifestFormula.INSTALLER_CERTIFICATE_NOT_EVALUATED;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import com.google.common.collect.ImmutableMap;
 
-import org.testng.annotations.Test;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
 
 import java.util.Arrays;
 import java.util.Collections;
 
+@RunWith(JUnit4.class)
 public class InstallerAllowedByManifestFormulaTest {
 
     private static final InstallerAllowedByManifestFormula
@@ -70,7 +75,7 @@
     }
 
     @Test
-    public void testFormulaMatches_certificateNotInManifest() {
+    public void testFormulaMatches_certificateDoesNotMatchManifest() {
         AppInstallMetadata appInstallMetadata = getAppInstallMetadataBuilder()
                 .setInstallerName("installer1")
                 .setInstallerCertificates(Arrays.asList("installer_cert3", "random_cert"))
@@ -92,6 +97,19 @@
         assertThat(FORMULA.matches(appInstallMetadata)).isTrue();
     }
 
+    @Test
+    public void testFormulaMatches_certificateNotSpecifiedInManifest() {
+        AppInstallMetadata appInstallMetadata = getAppInstallMetadataBuilder()
+                .setInstallerName("installer1")
+                .setInstallerCertificates(Arrays.asList("installer_cert3", "random_cert"))
+                .setAllowedInstallersAndCert(ImmutableMap.of(
+                        "installer1", INSTALLER_CERTIFICATE_NOT_EVALUATED,
+                        "installer2", "installer_cert1"
+                )).build();
+
+        assertThat(FORMULA.matches(appInstallMetadata)).isTrue();
+    }
+
     /** Returns a builder with all fields filled with some dummy data. */
     private AppInstallMetadata.Builder getAppInstallMetadataBuilder() {
         return new AppInstallMetadata.Builder()
diff --git a/media/java/android/media/tv/tuner/CasSessionRequest.aidl b/media/java/android/media/tv/tuner/CasSessionRequest.aidl
new file mode 100644
index 0000000..3dbf3d8
--- /dev/null
+++ b/media/java/android/media/tv/tuner/CasSessionRequest.aidl
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+package android.media.tv.tuner;
+
+/**
+ * A wrapper of a cas session requests that contains all the request info of the client.
+ *
+ * @hide
+ */
+parcelable CasSessionRequest;
\ No newline at end of file
diff --git a/media/java/android/media/tv/tuner/CasSessionRequest.java b/media/java/android/media/tv/tuner/CasSessionRequest.java
new file mode 100644
index 0000000..0f6a885
--- /dev/null
+++ b/media/java/android/media/tv/tuner/CasSessionRequest.java
@@ -0,0 +1,114 @@
+/*
+ * 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.
+ */
+
+package android.media.tv.tuner;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+/**
+ * Information required to request a Cas Session.
+ *
+ * @hide
+ */
+public final class CasSessionRequest implements Parcelable {
+    static final String TAG = "CasSessionRequest";
+
+    public static final
+                @NonNull
+                Parcelable.Creator<CasSessionRequest> CREATOR =
+                new Parcelable.Creator<CasSessionRequest>() {
+                @Override
+                public CasSessionRequest createFromParcel(Parcel source) {
+                    try {
+                        return new CasSessionRequest(source);
+                    } catch (Exception e) {
+                        Log.e(TAG, "Exception creating CasSessionRequest from parcel", e);
+                        return null;
+                    }
+                }
+
+                @Override
+                public CasSessionRequest[] newArray(int size) {
+                    return new CasSessionRequest[size];
+                }
+            };
+
+    /**
+     * Client id of the client that sends the request.
+     */
+    private final int mClientId;
+
+    /**
+     * System id of the requested cas.
+     */
+    private final int mCasSystemId;
+
+    private CasSessionRequest(@NonNull Parcel source) {
+        mClientId = source.readInt();
+        mCasSystemId = source.readInt();
+    }
+
+    /**
+     * Constructs a new {@link CasSessionRequest} with the given parameters.
+     *
+     * @param clientId id of the client.
+     * @param casSystemId the cas system id that the client is requesting.
+     */
+    public CasSessionRequest(int clientId,
+                             int casSystemId) {
+        mClientId = clientId;
+        mCasSystemId = casSystemId;
+    }
+
+    /**
+     * Returns the id of the client.
+     */
+    public int getClientId() {
+        return mClientId;
+    }
+
+    /**
+     * Returns the cas system id requested.
+     */
+    public int getCasSystemId() {
+        return mCasSystemId;
+    }
+
+    // Parcelable
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        StringBuilder b = new StringBuilder(128);
+        b.append("CasSessionRequest {clientId=").append(mClientId);
+        b.append(", casSystemId=").append(mCasSystemId);
+        b.append("}");
+        return b.toString();
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mClientId);
+        dest.writeInt(mCasSystemId);
+    }
+}
diff --git a/media/java/android/media/tv/tuner/ITunerResourceManager.aidl b/media/java/android/media/tv/tuner/ITunerResourceManager.aidl
new file mode 100644
index 0000000..758c689
--- /dev/null
+++ b/media/java/android/media/tv/tuner/ITunerResourceManager.aidl
@@ -0,0 +1,244 @@
+/*
+ * 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.
+ */
+
+package android.media.tv.tuner;
+
+import android.media.tv.tuner.CasSessionRequest;
+import android.media.tv.tuner.ITunerResourceManagerListener;
+import android.media.tv.tuner.ResourceClientProfile;
+import android.media.tv.tuner.TunerFrontendInfo;
+import android.media.tv.tuner.TunerFrontendRequest;
+import android.media.tv.tuner.TunerLnbRequest;
+
+/**
+ * Interface of the Tuner Resource Manager. It manages resources used by TV Tuners.
+ * <p>Resources include:
+ * <ul>
+ * <li>TunerFrontend {@link android.media.tv.tuner.frontend}.
+ * <li>TunerLnb {@link android.media.tv.tuner.Lnb}.
+ * <li>MediaCas {@link android.media.MediaCas}.
+ * <li>TvInputHardware {@link android.media.tv.TvInputHardwareInfo}.
+ * <ul>
+ *
+ * <p>Expected workflow is:
+ * <ul>
+ * <li>Tuner Java/MediaCas/TIF update resources of the current device with TRM.
+ * <li>Client registers its profile through {@link #registerClientProfile(ResourceClientProfile,
+ * ITunerResourceManagerListener, int[])}.
+ * <li>Client requests resources through request APIs.
+ * <li>If the resource needs to be handed to a higher priority client from a lower priority
+ * one, TRM calls ITunerResourceManagerListener registered by the lower priority client to release
+ * the resource.
+ * <ul>
+ *
+ * @hide
+ */
+interface ITunerResourceManager {
+    /*
+     * This API is used by the client to register their profile with the Tuner Resource manager.
+     *
+     * <p>The profile contains information that can show the base priority score of the client.
+     *
+     * @param profile {@link ResourceClientProfile} profile of the current client
+     * @param listener {@link ITunerResourceManagerListener} a callback to
+     *                 reclaim clients' resources when needed.
+     * @param clientId returns a clientId from the resource manager when the
+     *                 the client registers its profile.
+     */
+    void registerClientProfile(in ResourceClientProfile profile,
+        ITunerResourceManagerListener listener, out int[] clientId);
+
+    /*
+     * This API is used by the client to unregister their profile with the Tuner Resource manager.
+     *
+     * @param clientId the client id that needs to be unregistered.
+     */
+    void unregisterClientProfile(in int clientId);
+
+    /*
+     * Updates a registered client's priority and niceValue.
+     *
+     * @param clientId the id of the client that is updating its profile.
+     * @param priority the priority that the client would like to update to.
+     * @param niceValue the nice value that the client would like to update to.
+     *
+     * @return true if the update is successful.
+     */
+    boolean updateClientPriority(in int clientId, in int priority, in int niceValue);
+
+    /*
+     * Updates the available Frontend resources information on the current device.
+     *
+     * <p><strong>Note:</strong> This update must happen before the first
+     * {@link #requestFrontend(TunerFrontendRequest,int[])} and {@link #releaseFrontend(int)} call.
+     *
+     * @param infos an array of the available {@link TunerFrontendInfo} information.
+     */
+    void setFrontendInfoList(in TunerFrontendInfo[] infos);
+
+    /*
+     * Updates the available Cas resource information on the current device.
+     *
+     * <p><strong>Note:</strong> This update must happen before the first
+     * {@link #requestCasSession(CasSessionRequest, int[])} and {@link #releaseCasSession(int)} call.
+     *
+     * @param casSystemId id of the updating CAS system.
+     * @param maxSessionNum the max session number of the CAS system that is updated.
+     */
+    void updateCasInfo(in int casSystemId, in int maxSessionNum);
+
+    /*
+     * Updates the available Lnb resource information on the current device.
+     *
+     * <p><strong>Note:</strong> This update must happen before the first
+     * {@link #requestLnb(TunerLnbRequest, int[])} and {@link #releaseLnb(int)} call.
+     *
+     * @param lnbIds ids of the updating lnbs.
+     */
+    void setLnbInfoList(in int[] lnbIds);
+
+    /*
+     * This API is used by the Tuner framework to request an available frontend from the TunerHAL.
+     *
+     * <p>There are three possible scenarios:
+     * <ul>
+     * <li>If there is frontend available, the API would send the id back.
+     *
+     * <li>If no Frontend is available but the current request info can show higher priority than
+     * other uses of Frontend, the API will send
+     * {@link ITunerResourceManagerListener#onResourcesReclaim()} to the {@link Tuner}. Tuner would
+     * handle the resource reclaim on the holder of lower priority and notify the holder of its
+     * resource loss.
+     *
+     * <li>If no frontend can be granted, the API would return false.
+     * <ul>
+     *
+     * <p><strong>Note:</strong> {@link #setFrontendInfoList(TunerFrontendInfo[])} must be called
+     * before this request.
+     *
+     * @param request {@link TunerFrontendRequest} information of the current request.
+     * @param frontendId a one-element array to return the granted frontendId.
+     *
+     * @return true if there is frontend granted.
+     */
+    boolean requestFrontend(in TunerFrontendRequest request, out int[] frontendId);
+
+    /*
+     * Requests to share frontend with an existing client.
+     *
+     * <p><strong>Note:</strong> {@link #setFrontendInfoList(TunerFrontendInfo[])} must be called
+     * before this request.
+     *
+     * @param selfClientId the id of the client that sends the request.
+     * @param targetClientId the id of the client to share the frontend with.
+     */
+    void shareFrontend(in int selfClientId, in int targetClientId);
+
+    /*
+     * This API is used by the Tuner framework to request an available Cas session. This session
+     * needs to be under the CAS system with the id indicated in the {@code request}.
+     *
+     * <p>There are three possible scenarios:
+     * <ul>
+     * <li>If there is Cas session available, the API would send the id back.
+     *
+     * <li>If no Cas session is available but the current request info can show higher priority than
+     * other uses of the sessions under the requested CAS system, the API will send
+     * {@link ITunerResourceManagerCallback#onResourcesReclaim()} to the {@link Tuner}. Tuner would
+     * handle the resource reclaim on the holder of lower priority and notify the holder of its
+     * resource loss.
+     *
+     * <li>If no Cas session can be granted, the API would return false.
+     * <ul>
+     *
+     * <p><strong>Note:</strong> {@link #updateCasInfo(int, int)} must be called before this request.
+     *
+     * @param request {@link CasSessionRequest} information of the current request.
+     * @param sessionResourceId a one-element array to return the granted cas session id.
+     *
+     * @return true if there is CAS session granted.
+     */
+    boolean requestCasSession(in CasSessionRequest request, out int[] sessionResourceId);
+
+    /*
+     * This API is used by the Tuner framework to request an available Lnb from the TunerHAL.
+     *
+     * <p>There are three possible scenarios:
+     * <ul>
+     * <li>If there is Lnb available, the API would send the id back.
+     *
+     * <li>If no Lnb is available but the current request has a higher priority than other uses of
+     * lnbs, the API will send {@link ITunerResourceManagerCallback#onResourcesReclaim()} to the
+     * {@link Tuner}. Tuner would handle the resource reclaim on the holder of lower priority and
+     * notify the holder of its resource loss.
+     *
+     * <li>If no Lnb system can be granted, the API would return false.
+     * <ul>
+     *
+     * <p><strong>Note:</strong> {@link #setLnbInfos(int[])} must be called before this request.
+     *
+     * @param request {@link TunerLnbRequest} information of the current request.
+     * @param lnbId a one-element array to return the granted Lnb id.
+     *
+     * @return true if there is Lnb granted.
+     */
+    boolean requestLnb(in TunerLnbRequest request, out int[] lnbId);
+
+    /*
+     * Notifies the TRM that the given frontend has been released.
+     *
+     * <p>Client must call this whenever it releases a Tuner frontend.
+     *
+     * <p><strong>Note:</strong> {@link #setFrontendInfoList(TunerFrontendInfo[])} must be called
+     * before this release.
+     *
+     * @param frontendId the id of the released frontend.
+     */
+    void releaseFrontend(in int frontendId);
+
+    /*
+     * Notifies the TRM that the given Cas session has been released.
+     *
+     * <p>Client must call this whenever it releases a Cas session.
+     *
+     * <p><strong>Note:</strong> {@link #updateCasInfo(int, int)} must be called before this release.
+     *
+     * @param sessionResourceId the id of the released CAS session.
+     */
+    void releaseCasSession(in int sessionResourceId);
+
+    /*
+     * Notifies the TRM that the Lnb with the given id was released.
+     *
+     * <p>Client must call this whenever it releases an Lnb.
+     *
+     * <p><strong>Note:</strong> {@link #setLnbInfos(int[])} must be called before this release.
+     *
+     * @param lnbId the id of the released Tuner Lnb.
+     */
+    void releaseLnb(in int lnbId);
+
+    /*
+     * Compare two clients' priority.
+     *
+     * @param challengerProfile the {@link ResourceClientProfile} of the challenger.
+     * @param holderProfile the {@link ResourceClientProfile} of the holder of the resource.
+     *
+     * @return true if the challenger has higher priority than the holder.
+     */
+    boolean isHigherPriority(in ResourceClientProfile challengerProfile,
+            in ResourceClientProfile holderProfile);
+}
diff --git a/media/java/android/media/tv/tuner/ITunerResourceManagerListener.aidl b/media/java/android/media/tv/tuner/ITunerResourceManagerListener.aidl
new file mode 100644
index 0000000..557032c
--- /dev/null
+++ b/media/java/android/media/tv/tuner/ITunerResourceManagerListener.aidl
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+package android.media.tv.tuner;
+
+/**
+ * Interface to receive callbacks from ITunerResourceManager.
+ *
+ * @hide
+ */
+oneway interface ITunerResourceManagerListener {
+    /*
+     * TRM invokes this method when the client's resources need to be reclaimed.
+     *
+     * <p>This method is implemented in Tuner Framework to take the reclaiming
+     * actions. It's a synchonized call. TRM would wait on the call to finish
+     * then grant the resource.
+     */
+    void onResourcesReclaim();
+}
\ No newline at end of file
diff --git a/media/java/android/media/tv/tuner/ResourceClientProfile.aidl b/media/java/android/media/tv/tuner/ResourceClientProfile.aidl
new file mode 100644
index 0000000..da3c5c4
--- /dev/null
+++ b/media/java/android/media/tv/tuner/ResourceClientProfile.aidl
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+package android.media.tv.tuner;
+
+/**
+ * A profile of a resource client. This profile is used to register the client info
+ * with the Tuner Resource Manager.
+ *
+ * @hide
+ */
+parcelable ResourceClientProfile;
\ No newline at end of file
diff --git a/media/java/android/media/tv/tuner/ResourceClientProfile.java b/media/java/android/media/tv/tuner/ResourceClientProfile.java
new file mode 100644
index 0000000..e203135
--- /dev/null
+++ b/media/java/android/media/tv/tuner/ResourceClientProfile.java
@@ -0,0 +1,129 @@
+/*
+ * 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.
+ */
+
+package android.media.tv.tuner;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+/**
+ * A profile of a resource client. This profile is used to register the client info
+ * with the Tuner Resource Manager(TRM).
+ *
+ * @hide
+ */
+public final class ResourceClientProfile implements Parcelable {
+    static final String TAG = "ResourceClientProfile";
+
+    public static final
+                @NonNull
+                Parcelable.Creator<ResourceClientProfile> CREATOR =
+                new Parcelable.Creator<ResourceClientProfile>() {
+                @Override
+                public ResourceClientProfile createFromParcel(Parcel source) {
+                    try {
+                        return new ResourceClientProfile(source);
+                    } catch (Exception e) {
+                        Log.e(TAG, "Exception creating ResourceClientProfile from parcel", e);
+                        return null;
+                    }
+                }
+
+                @Override
+                public ResourceClientProfile[] newArray(int size) {
+                    return new ResourceClientProfile[size];
+                }
+            };
+
+    /**
+     * This is used by TRM to get TV App’s processId from TIF.
+     * The processId will be used to identify foreground applications.
+     *
+     * <p>MediaCas, Tuner and TvInputHardwareManager get tvInputSessionId from TIS.
+     * If mTvInputSessionId is UNKNOWN, the client is always background.
+     */
+    private final String mTvInputSessionId;
+
+    /**
+     * Usage of the client.
+     */
+    private final int mUseCase;
+
+    private ResourceClientProfile(@NonNull Parcel source) {
+        mTvInputSessionId = source.readString();
+        mUseCase = source.readInt();
+    }
+
+    /**
+     * Constructs a new {@link ResourceClientProfile} with the given parameters.
+     *
+     * @param tvInputSessionId the unique id of the session owned by the client.
+     * @param useCase the usage of the client. Suggested priority hints are
+     *                {@link android.media.tv.TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK}
+     *                {@link android.media.tv.TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE}
+     *                {@link android.media.tv.TvInputService.PRIORITY_HINT_USE_CASE_TYPE_RECORD}.
+     *                New [use case : priority value] pair can be defined in the manifest by the
+     *                OEM. Any undefined use case would cause IllegalArgumentException.
+     */
+    public ResourceClientProfile(@NonNull String tvInputSessionId,
+                                 int useCase) {
+        mTvInputSessionId = tvInputSessionId;
+        mUseCase = useCase;
+    }
+
+    /**
+     * Returns the tv input session id of the client.
+     *
+     * @return the value of the tv input session id.
+     */
+    @NonNull
+    public String getTvInputSessionId() {
+        return mTvInputSessionId;
+    }
+
+    /**
+     * Returns the user usage of the client.
+     *
+     * @return the value of use case.
+     */
+    public int getUseCase() {
+        return mUseCase;
+    }
+
+    // Parcelable
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        StringBuilder b = new StringBuilder(128);
+        b.append("ResourceClientProfile {tvInputSessionId=").append(mTvInputSessionId);
+        b.append(", useCase=").append(mUseCase);
+        b.append("}");
+        return b.toString();
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(mTvInputSessionId);
+        dest.writeInt(mUseCase);
+    }
+}
diff --git a/media/java/android/media/tv/tuner/TunerFrontendInfo.aidl b/media/java/android/media/tv/tuner/TunerFrontendInfo.aidl
new file mode 100644
index 0000000..012e051
--- /dev/null
+++ b/media/java/android/media/tv/tuner/TunerFrontendInfo.aidl
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+package android.media.tv.tuner;
+
+/**
+ * Simple container of the FrontendInfo struct defined in the TunerHAL 1.0 interface.
+ *
+ * @hide
+ */
+parcelable TunerFrontendInfo;
\ No newline at end of file
diff --git a/media/java/android/media/tv/tuner/TunerFrontendInfo.java b/media/java/android/media/tv/tuner/TunerFrontendInfo.java
new file mode 100644
index 0000000..a62ecb3
--- /dev/null
+++ b/media/java/android/media/tv/tuner/TunerFrontendInfo.java
@@ -0,0 +1,141 @@
+/*
+ * 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.
+ */
+
+package android.media.tv.tuner;
+
+import android.annotation.NonNull;
+import android.media.tv.tuner.frontend.FrontendSettings.Type;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+/**
+ * Simple container of the FrontendInfo struct defined in the TunerHAL 1.0 interface.
+ *
+ * <p>Note that this object is defined to pass necessary frontend info between the
+ * Tuner Resource Manager and the client. It includes partial information in
+ * {@link FrontendInfo}.
+ *
+ * @hide
+ */
+public final class TunerFrontendInfo implements Parcelable {
+    static final String TAG = "TunerFrontendInfo";
+
+    public static final
+            @NonNull
+            Parcelable.Creator<TunerFrontendInfo> CREATOR =
+            new Parcelable.Creator<TunerFrontendInfo>() {
+                @Override
+                public TunerFrontendInfo createFromParcel(Parcel source) {
+                    try {
+                        return new TunerFrontendInfo(source);
+                    } catch (Exception e) {
+                        Log.e(TAG, "Exception creating TunerFrontendInfo from parcel", e);
+                        return null;
+                    }
+                }
+
+                @Override
+                public TunerFrontendInfo[] newArray(int size) {
+                    return new TunerFrontendInfo[size];
+                }
+            };
+
+    private final int mId;
+
+    @Type
+    private final int mFrontendType;
+
+    /**
+     * Frontends are assigned with the same exclusiveGroupId if they can't
+     * function at same time. For instance, they share same hardware module.
+     */
+    private final int mExclusiveGroupId;
+
+    private TunerFrontendInfo(@NonNull Parcel source) {
+        mId = source.readInt();
+        mFrontendType = source.readInt();
+        mExclusiveGroupId = source.readInt();
+    }
+
+    /**
+     * Constructs a new {@link TunerFrontendInfo} with the given parameters.
+     *
+     * @param frontendType the type of the frontend.
+     * @param exclusiveGroupId the group id of the frontend. FE with the same
+                               group id can't function at the same time.
+     */
+    public TunerFrontendInfo(int id,
+                             @Type int frontendType,
+                             int exclusiveGroupId) {
+        mId = id;
+        mFrontendType = frontendType;
+        mExclusiveGroupId = exclusiveGroupId;
+    }
+
+    /**
+     * Returns the frontend id.
+     *
+     * @return the value of the frontend id.
+     */
+    public int getId() {
+        return mId;
+    }
+
+    /**
+     * Returns the application id that requests the tuner frontend resource.
+     *
+     * @return the value of the frontend type.
+     */
+    @Type
+    public int getFrontendType() {
+        return mFrontendType;
+    }
+
+    /**
+     * Returns the exclusiveGroupId. Frontends with the same exclusiveGroupId
+     * can't function at same time.
+     *
+     * @return the value of the exclusive group id.
+     */
+    public int getExclusiveGroupId() {
+        return mExclusiveGroupId;
+    }
+
+    // Parcelable
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        StringBuilder b = new StringBuilder(128);
+        b.append("TunerFrontendInfo {id=").append(mId);
+        b.append(", frontendType=").append(mFrontendType);
+        b.append(", exclusiveGroupId=").append(mExclusiveGroupId);
+        b.append("}");
+        return b.toString();
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mId);
+        dest.writeInt(mFrontendType);
+        dest.writeInt(mExclusiveGroupId);
+    }
+}
diff --git a/media/java/android/media/tv/tuner/TunerFrontendRequest.aidl b/media/java/android/media/tv/tuner/TunerFrontendRequest.aidl
new file mode 100644
index 0000000..25c298f
--- /dev/null
+++ b/media/java/android/media/tv/tuner/TunerFrontendRequest.aidl
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+package android.media.tv.tuner;
+
+/**
+ * Information required to request a Tuner Frontend.
+ *
+ * @hide
+ */
+parcelable TunerFrontendRequest;
\ No newline at end of file
diff --git a/media/java/android/media/tv/tuner/TunerFrontendRequest.java b/media/java/android/media/tv/tuner/TunerFrontendRequest.java
new file mode 100644
index 0000000..01a0a09
--- /dev/null
+++ b/media/java/android/media/tv/tuner/TunerFrontendRequest.java
@@ -0,0 +1,114 @@
+/*
+ * 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.
+ */
+
+package android.media.tv.tuner;
+
+import android.annotation.NonNull;
+import android.media.tv.tuner.frontend.FrontendSettings.Type;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+/**
+ * Information required to request a Tuner Frontend.
+ *
+ * @hide
+ */
+public final class TunerFrontendRequest implements Parcelable {
+    static final String TAG = "TunerFrontendRequest";
+
+    public static final
+            @NonNull
+            Parcelable.Creator<TunerFrontendRequest> CREATOR =
+            new Parcelable.Creator<TunerFrontendRequest>() {
+                @Override
+                public TunerFrontendRequest createFromParcel(Parcel source) {
+                    try {
+                        return new TunerFrontendRequest(source);
+                    } catch (Exception e) {
+                        Log.e(TAG, "Exception creating TunerFrontendRequest from parcel", e);
+                        return null;
+                    }
+                }
+
+                @Override
+                public TunerFrontendRequest[] newArray(int size) {
+                    return new TunerFrontendRequest[size];
+                }
+            };
+
+    private final int mClientId;
+    @Type
+    private final int mFrontendType;
+
+    private TunerFrontendRequest(@NonNull Parcel source) {
+        mClientId = source.readInt();
+        mFrontendType = source.readInt();
+    }
+
+    /**
+     * Constructs a new {@link TunerFrontendRequest} with the given parameters.
+     *
+     * @param clientId the unique id of the client returned when registering profile.
+     * @param frontendType the type of the requested frontend.
+     */
+    public TunerFrontendRequest(int clientId,
+                                @Type int frontendType) {
+        mClientId = clientId;
+        mFrontendType = frontendType;
+    }
+
+    /**
+     * Returns the client id that requests the tuner frontend resource.
+     *
+     * @return the value of the client id.
+     */
+    public int getClientId() {
+        return mClientId;
+    }
+
+    /**
+     * Returns the frontend type that the client requests for.
+     *
+     * @return the value of the requested frontend type.
+     */
+    @Type
+    public int getFrontendType() {
+        return mFrontendType;
+    }
+
+    // Parcelable
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        StringBuilder b = new StringBuilder(128);
+        b.append("TunerFrontendRequest {clientId=").append(mClientId);
+        b.append(", frontendType=").append(mFrontendType);
+        b.append("}");
+        return b.toString();
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mClientId);
+        dest.writeInt(mFrontendType);
+    }
+}
diff --git a/media/java/android/media/tv/tuner/TunerLnbRequest.aidl b/media/java/android/media/tv/tuner/TunerLnbRequest.aidl
new file mode 100644
index 0000000..b811e39
--- /dev/null
+++ b/media/java/android/media/tv/tuner/TunerLnbRequest.aidl
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+package android.media.tv.tuner;
+
+/**
+ * Information required to request a Tuner Lnb.
+ *
+ * @hide
+ */
+parcelable TunerLnbRequest;
\ No newline at end of file
diff --git a/media/java/android/media/tv/tuner/TunerLnbRequest.java b/media/java/android/media/tv/tuner/TunerLnbRequest.java
new file mode 100644
index 0000000..60cd790
--- /dev/null
+++ b/media/java/android/media/tv/tuner/TunerLnbRequest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.
+ */
+
+package android.media.tv.tuner;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+/**
+ * Information required to request a Tuner Lnb.
+ *
+ * @hide
+ */
+public final class TunerLnbRequest implements Parcelable {
+    static final String TAG = "TunerLnbRequest";
+
+    public static final
+            @NonNull
+                Parcelable.Creator<TunerLnbRequest> CREATOR =
+                new Parcelable.Creator<TunerLnbRequest>() {
+                @Override
+                public TunerLnbRequest createFromParcel(Parcel source) {
+                    try {
+                        return new TunerLnbRequest(source);
+                    } catch (Exception e) {
+                        Log.e(TAG, "Exception creating TunerLnbRequest from parcel", e);
+                        return null;
+                    }
+                }
+
+                @Override
+                public TunerLnbRequest[] newArray(int size) {
+                    return new TunerLnbRequest[size];
+                }
+            };
+
+    /**
+     * Client id of the client that sends the request.
+     */
+    private final int mClientId;
+
+    private TunerLnbRequest(@NonNull Parcel source) {
+        mClientId = source.readInt();
+    }
+
+    /**
+     * Constructs a new {@link TunerLnbRequest} with the given parameters.
+     *
+     * @param clientId the id of the client.
+     */
+    public TunerLnbRequest(int clientId) {
+        mClientId = clientId;
+    }
+
+    /**
+     * Returns the id of the client
+     */
+    public int getClientId() {
+        return mClientId;
+    }
+
+    // Parcelable
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        StringBuilder b = new StringBuilder(128);
+        b.append("TunerLnbRequest {clientId=").append(mClientId);
+        b.append("}");
+        return b.toString();
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mClientId);
+    }
+}
diff --git a/media/java/android/media/tv/tuner/TunerResourceManager.java b/media/java/android/media/tv/tuner/TunerResourceManager.java
new file mode 100644
index 0000000..68ca572
--- /dev/null
+++ b/media/java/android/media/tv/tuner/TunerResourceManager.java
@@ -0,0 +1,407 @@
+/*
+ * 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.
+ */
+
+package android.media.tv.tuner;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresFeature;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Interface of the Tuner Resource Manager(TRM). It manages resources used by TV Tuners.
+ * <p>Resources include:
+ * <ul>
+ * <li>TunerFrontend {@link android.media.tv.tuner.frontend}.
+ * <li>TunerLnb {@link android.media.tv.tuner.Lnb}.
+ * <li>MediaCas {@link android.media.MediaCas}.
+ * <ul>
+ *
+ * <p>Expected workflow is:
+ * <ul>
+ * <li>Tuner Java/MediaCas/TIF update resources of the current device with TRM.
+ * <li>Client registers its profile through {@link #registerClientProfile(ResourceClientProfile,
+ * Executor, ResourceListener, int[])}.
+ * <li>Client requests resources through request APIs.
+ * <li>If the resource needs to be handed to a higher priority client from a lower priority
+ * one, TRM calls ITunerResourceManagerListener registered by the lower priority client to release
+ * the resource.
+ * <ul>
+ *
+ * <p>TRM also exposes its priority comparison algorithm as a helping method to other services.
+ * {@see #isHigherPriority(ResourceClientProfile, ResourceClientProfile)}.
+ *
+ * @hide
+ */
+@RequiresFeature(PackageManager.FEATURE_LIVE_TV)
+@SystemService(Context.TV_TUNER_RESOURCE_MGR_SERVICE)
+public class TunerResourceManager {
+    private static final String TAG = "TunerResourceManager";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    public static final int INVALID_FRONTEND_ID = -1;
+    public static final int INVALID_CAS_SESSION_RESOURCE_ID = -1;
+    public static final int INVALID_LNB_ID = -1;
+    public static final int INVALID_TV_INPUT_DEVICE_ID = -1;
+    public static final int INVALID_TV_INPUT_PORT_ID = -1;
+
+    private final ITunerResourceManager mService;
+    private final int mUserId;
+
+    /**
+     * @hide
+     */
+    public TunerResourceManager(ITunerResourceManager service, int userId) {
+        mService = service;
+        mUserId = userId;
+    }
+
+    /**
+     * This API is used by the client to register their profile with the Tuner Resource manager.
+     *
+     * <p>The profile contains information that can show the base priority score of the client.
+     *
+     * @param profile {@link ResourceClientProfile} profile of the current client. Undefined use
+     *                case would cause IllegalArgumentException.
+     * @param executor the executor on which the listener would be invoked.
+     * @param listener {@link ResourceListener} callback to reclaim clients' resources when needed.
+     * @param clientId returned a clientId from the resource manager when the
+     *                 the client registeres.
+     * @throws IllegalArgumentException when {@code profile} contains undefined use case.
+     */
+    public void registerClientProfile(@NonNull ResourceClientProfile profile,
+                        @NonNull @CallbackExecutor Executor executor,
+                        @NonNull ResourceListener listener,
+                        @NonNull int[] clientId) {
+        // TODO: throw new IllegalArgumentException("Unknown client use case")
+        // when the use case is not defined.
+        try {
+            mService.registerClientProfile(profile,
+                    new ITunerResourceManagerListener.Stub() {
+                    @Override
+                public void onResourcesReclaim() {
+                        final long identity = Binder.clearCallingIdentity();
+                        try {
+                            executor.execute(() -> listener.onResourcesReclaim());
+                        } finally {
+                            Binder.restoreCallingIdentity(identity);
+                        }
+                    }
+                }, clientId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * This API is used by the client to unregister their profile with the
+     * Tuner Resource manager.
+     *
+     * @param clientId the client id that needs to be unregistered.
+     */
+    public void unregisterClientProfile(int clientId) {
+        try {
+            mService.unregisterClientProfile(clientId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * This API is used by client to update its registered {@link ResourceClientProfile}.
+     *
+     * <p>We recommend creating a new tuner instance for different use cases instead of using this
+     * API since different use cases may need different resources.
+     *
+     * <p>If TIS updates use case, it needs to ensure underneath resources are exchangeable between
+     * two different use cases.
+     *
+     * <p>Only the arbitrary priority and niceValue are allowed to be updated.
+     *
+     * @param clientId the id of the client that is updating its profile.
+     * @param priority the priority that the client would like to update to.
+     * @param niceValue the nice value that the client would like to update to.
+     *
+     * @return true if the update is successful.
+     */
+    public boolean updateClientPriority(int clientId, int priority, int niceValue) {
+        boolean result = false;
+        try {
+            result = mService.updateClientPriority(clientId, priority, niceValue);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        return result;
+    }
+
+    /**
+     * Updates the current TRM of the TunerHAL Frontend information.
+     *
+     * <p><strong>Note:</strong> This update must happen before the first
+     * {@link #requestFrontend(TunerFrontendRequest, int[])} and {@link #releaseFrontend(int)} call.
+     *
+     * @param infos an array of the available {@link TunerFrontendInfo} information.
+     */
+    public void setFrontendInfoList(@NonNull TunerFrontendInfo[] infos) {
+        try {
+            mService.setFrontendInfoList(infos);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Updates the TRM of the current CAS information.
+     *
+     * <p><strong>Note:</strong> This update must happen before the first
+     * {@link #requestCasSession(CasSessionRequest, int[])} and {@link #releaseCasSession(int)}
+     * call.
+     *
+     * @param casSystemId id of the updating CAS system.
+     * @param maxSessionNum the max session number of the CAS system that is updated.
+     */
+    public void updateCasInfo(int casSystemId, int maxSessionNum) {
+        try {
+            mService.updateCasInfo(casSystemId, maxSessionNum);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Updates the TRM of the current Lnb information.
+     *
+     * <p><strong>Note:</strong> This update must happen before the first
+     * {@link #requestLnb(TunerLnbRequest, int[])} and {@link #releaseLnb(int)} call.
+     *
+     * @param lnbIds ids of the updating lnbs.
+     */
+    public void setLnbInfoList(int[] lnbIds) {
+        try {
+            mService.setLnbInfoList(lnbIds);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Requests a frontend resource.
+     *
+     * <p>There are three possible scenarios:
+     * <ul>
+     * <li>If there is frontend available, the API would send the id back.
+     *
+     * <li>If no Frontend is available but the current request info can show higher priority than
+     * other uses of Frontend, the API will send
+     * {@link ITunerResourceManagerListener#onResourcesReclaim()} to the {@link Tuner}. Tuner would
+     * handle the resource reclaim on the holder of lower priority and notify the holder of its
+     * resource loss.
+     *
+     * <li>If no frontend can be granted, the API would return false.
+     * <ul>
+     *
+     * <p><strong>Note:</strong> {@link #setFrontendInfoList(TunerFrontendInfo[])} must be called
+     * before this request.
+     *
+     * @param request {@link TunerFrontendRequest} information of the current request.
+     * @param frontendId a one-element array to return the granted frontendId. If
+     *                   no frontend granted, this will return {@link #INVALID_FRONTEND_ID}.
+     *
+     * @return true if there is frontend granted.
+     */
+    public boolean requestFrontend(@NonNull TunerFrontendRequest request,
+                @Nullable int[] frontendId) {
+        boolean result = false;
+        try {
+            result = mService.requestFrontend(request, frontendId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        return result;
+    }
+
+    /**
+     * Requests from the client to share frontend with an existing client.
+     *
+     * <p><strong>Note:</strong> {@link #setFrontendInfoList(TunerFrontendInfo[])} must be called
+     * before this request.
+     *
+     * @param selfClientId the id of the client that sends the request.
+     * @param targetClientId the id of the client to share the frontend with.
+     */
+    public void shareFrontend(int selfClientId, int targetClientId) {
+        try {
+            mService.shareFrontend(selfClientId, targetClientId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Requests a CAS session resource.
+     *
+     * <p>There are three possible scenarios:
+     * <ul>
+     * <li>If there is Cas session available, the API would send the id back.
+     *
+     * <li>If no Cas system is available but the current request info can show higher priority than
+     * other uses of the cas sessions under the requested cas system, the API will send
+     * {@link ITunerResourceManagerListener#onResourcesReclaim()} to the {@link Tuner}. Tuner would
+     * handle the resource reclaim on the holder of lower priority and notify the holder of its
+     * resource loss.
+     *
+     * <p><strong>Note:</strong> {@link #updateCasInfo(int, int)} must be called before this
+     * request.
+     *
+     * @param request {@link CasSessionRequest} information of the current request.
+     * @param sessionResourceId a one-element array to return the granted cas session id.
+     *                          If no CAS granted, this will return
+     *                          {@link #INVALID_CAS_SESSION_RESOURCE_ID}.
+     *
+     * @return true if there is CAS session granted.
+     */
+    public boolean requestCasSession(@NonNull CasSessionRequest request,
+                @NonNull int[] sessionResourceId) {
+        boolean result = false;
+        try {
+            result = mService.requestCasSession(request, sessionResourceId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        return result;
+    }
+
+    /**
+     * Requests a Tuner Lnb resource.
+     *
+     * <p>There are three possible scenarios:
+     * <ul>
+     * <li>If there is Lnb available, the API would send the id back.
+     *
+     * <li>If no Lnb is available but the current request has a higher priority than other uses of
+     * lnbs, the API will send {@link ITunerResourceManagerListener#onResourcesReclaim()} to the
+     * {@link Tuner}. Tuner would handle the resource reclaim on the holder of lower priority and
+     * notify the holder of its resource loss.
+     *
+     * <li>If no Lnb system can be granted, the API would return false.
+     * <ul>
+     *
+     * <p><strong>Note:</strong> {@link #setLnbInfos(int[])} must be called before this request.
+     *
+     * @param request {@link TunerLnbRequest} information of the current request.
+     * @param lnbId a one-element array to return the granted Lnb id.
+     *              If no Lnb granted, this will return {@link #INVALID_LNB_ID}.
+     *
+     * @return true if there is Lnb granted.
+     */
+    public boolean requestLnb(@NonNull TunerLnbRequest request, @NonNull int[] lnbId) {
+        boolean result = false;
+        try {
+            result = mService.requestLnb(request, lnbId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        return result;
+    }
+
+    /**
+     * Notifies the TRM that the given frontend has been released.
+     *
+     * <p>Client must call this whenever it releases a Tuner frontend.
+     *
+     * <p><strong>Note:</strong> {@link #setFrontendInfoList(TunerFrontendInfo[])} must be called
+     * before this release.
+     *
+     * @param frontendId the id of the released frontend.
+     */
+    public void releaseFrontend(int frontendId) {
+        try {
+            mService.releaseFrontend(frontendId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Notifies the TRM that the given Cas session has been released.
+     *
+     * <p>Client must call this whenever it releases a Cas session.
+     *
+     * <p><strong>Note:</strong> {@link #updateCasInfo(int, int)} must be called before this
+     * release.
+     *
+     * @param sessionResourceId the id of the released CAS session.
+     */
+    public void releaseCasSession(int sessionResourceId) {
+        try {
+            mService.releaseCasSession(sessionResourceId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Notifies the TRM that the Lnb with the given id has been released.
+     *
+     * <p>Client must call this whenever it releases an Lnb.
+     *
+     * <p><strong>Note:</strong> {@link #setLnbInfos(int[])} must be called before this release.
+     *
+     * @param lnbId the id of the released Tuner Lnb.
+     */
+    public void releaseLnb(int lnbId) {
+        try {
+            mService.releaseLnb(lnbId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Compare two clients' priority.
+     *
+     * @param challengerProfile the {@link ResourceClientProfile} of the challenger.
+     * @param holderProfile the {@link ResourceClientProfile} of the holder of the resource.
+     *
+     * @return true if the challenger has higher priority than the holder.
+     */
+    public boolean isHigherPriority(ResourceClientProfile challengerProfile,
+            ResourceClientProfile holderProfile) {
+        try {
+            return mService.isHigherPriority(challengerProfile, holderProfile);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Interface used to receive events from TunerResourceManager.
+     */
+    public abstract static class ResourceListener {
+        /*
+         * To reclaim all the resources of the callack owner.
+         */
+        public abstract void onResourcesReclaim();
+    }
+}
diff --git a/packages/PrintSpooler/res/values-ja/donottranslate.xml b/packages/PrintSpooler/res/values-ja/donottranslate.xml
index d334ddd..6a0f768 100644
--- a/packages/PrintSpooler/res/values-ja/donottranslate.xml
+++ b/packages/PrintSpooler/res/values-ja/donottranslate.xml
@@ -16,7 +16,7 @@
 
 <resources>
 
-    <string name="mediasize_default">JIS_B5</string>
+    <string name="mediasize_default">ISO_A4</string>
     <string name="mediasize_standard">@string/mediasize_standard_japan</string>
 
 </resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index a1342ec..984ab11 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -35,6 +35,7 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
  * LocalMediaManager provide interface to get MediaDevice list and transfer media to MediaDevice.
@@ -53,7 +54,7 @@
         int STATE_DISCONNECTED = 3;
     }
 
-    private final Collection<DeviceCallback> mCallbacks = new ArrayList<>();
+    private final Collection<DeviceCallback> mCallbacks = new CopyOnWriteArrayList<>();
     @VisibleForTesting
     final MediaDeviceCallback mMediaDeviceCallback = new MediaDeviceCallback();
 
@@ -73,18 +74,14 @@
      * Register to start receiving callbacks for MediaDevice events.
      */
     public void registerCallback(DeviceCallback callback) {
-        synchronized (mCallbacks) {
-            mCallbacks.add(callback);
-        }
+        mCallbacks.add(callback);
     }
 
     /**
      * Unregister to stop receiving callbacks for MediaDevice events
      */
     public void unregisterCallback(DeviceCallback callback) {
-        synchronized (mCallbacks) {
-            mCallbacks.remove(callback);
-        }
+        mCallbacks.remove(callback);
     }
 
     public LocalMediaManager(Context context, String packageName, Notification notification) {
@@ -152,10 +149,8 @@
     }
 
     void dispatchSelectedDeviceStateChanged(MediaDevice device, @MediaDeviceState int state) {
-        synchronized (mCallbacks) {
-            for (DeviceCallback callback : mCallbacks) {
-                callback.onSelectedDeviceStateChanged(device, state);
-            }
+        for (DeviceCallback callback : getCallbacks()) {
+            callback.onSelectedDeviceStateChanged(device, state);
         }
     }
 
@@ -169,19 +164,15 @@
     }
 
     void dispatchDeviceListUpdate() {
-        synchronized (mCallbacks) {
-            Collections.sort(mMediaDevices, COMPARATOR);
-            for (DeviceCallback callback : mCallbacks) {
-                callback.onDeviceListUpdate(new ArrayList<>(mMediaDevices));
-            }
+        Collections.sort(mMediaDevices, COMPARATOR);
+        for (DeviceCallback callback : getCallbacks()) {
+            callback.onDeviceListUpdate(new ArrayList<>(mMediaDevices));
         }
     }
 
     void dispatchDeviceAttributesChanged() {
-        synchronized (mCallbacks) {
-            for (DeviceCallback callback : mCallbacks) {
-                callback.onDeviceAttributesChanged();
-            }
+        for (DeviceCallback callback : getCallbacks()) {
+            callback.onDeviceAttributesChanged();
         }
     }
 
@@ -270,6 +261,10 @@
                 || device.isActiveDevice(BluetoothProfile.HEARING_AID);
     }
 
+    private Collection<DeviceCallback> getCallbacks() {
+        return new CopyOnWriteArrayList<>(mCallbacks);
+    }
+
     class MediaDeviceCallback implements MediaManager.MediaDeviceCallback {
         @Override
         public void onDeviceAdded(MediaDevice device) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java
index 7898982..73551f6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java
@@ -22,6 +22,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
  * MediaManager provide interface to get MediaDevice list.
@@ -30,7 +31,7 @@
 
     private static final String TAG = "MediaManager";
 
-    protected final Collection<MediaDeviceCallback> mCallbacks = new ArrayList<>();
+    protected final Collection<MediaDeviceCallback> mCallbacks = new CopyOnWriteArrayList<>();
     protected final List<MediaDevice> mMediaDevices = new ArrayList<>();
 
     protected Context mContext;
@@ -42,18 +43,14 @@
     }
 
     protected void registerCallback(MediaDeviceCallback callback) {
-        synchronized (mCallbacks) {
-            if (!mCallbacks.contains(callback)) {
-                mCallbacks.add(callback);
-            }
+        if (!mCallbacks.contains(callback)) {
+            mCallbacks.add(callback);
         }
     }
 
     protected void unregisterCallback(MediaDeviceCallback callback) {
-        synchronized (mCallbacks) {
-            if (mCallbacks.contains(callback)) {
-                mCallbacks.remove(callback);
-            }
+        if (mCallbacks.contains(callback)) {
+            mCallbacks.remove(callback);
         }
     }
 
@@ -78,53 +75,45 @@
     }
 
     protected void dispatchDeviceAdded(MediaDevice mediaDevice) {
-        synchronized (mCallbacks) {
-            for (MediaDeviceCallback callback : mCallbacks) {
-                callback.onDeviceAdded(mediaDevice);
-            }
+        for (MediaDeviceCallback callback : getCallbacks()) {
+            callback.onDeviceAdded(mediaDevice);
         }
     }
 
     protected void dispatchDeviceRemoved(MediaDevice mediaDevice) {
-        synchronized (mCallbacks) {
-            for (MediaDeviceCallback callback : mCallbacks) {
-                callback.onDeviceRemoved(mediaDevice);
-            }
+        for (MediaDeviceCallback callback : getCallbacks()) {
+            callback.onDeviceRemoved(mediaDevice);
         }
     }
 
     protected void dispatchDeviceListAdded() {
-        synchronized (mCallbacks) {
-            for (MediaDeviceCallback callback : mCallbacks) {
-                callback.onDeviceListAdded(new ArrayList<>(mMediaDevices));
-            }
+        for (MediaDeviceCallback callback : getCallbacks()) {
+            callback.onDeviceListAdded(new ArrayList<>(mMediaDevices));
         }
     }
 
     protected void dispatchDeviceListRemoved(List<MediaDevice> devices) {
-        synchronized (mCallbacks) {
-            for (MediaDeviceCallback callback : mCallbacks) {
-                callback.onDeviceListRemoved(devices);
-            }
+        for (MediaDeviceCallback callback : getCallbacks()) {
+            callback.onDeviceListRemoved(devices);
         }
     }
 
     protected void dispatchConnectedDeviceChanged(String id) {
-        synchronized (mCallbacks) {
-            for (MediaDeviceCallback callback : mCallbacks) {
-                callback.onConnectedDeviceChanged(id);
-            }
+        for (MediaDeviceCallback callback : getCallbacks()) {
+            callback.onConnectedDeviceChanged(id);
         }
     }
 
     protected void dispatchDataChanged() {
-        synchronized (mCallbacks) {
-            for (MediaDeviceCallback callback : mCallbacks) {
-                callback.onDeviceAttributesChanged();
-            }
+        for (MediaDeviceCallback callback : getCallbacks()) {
+            callback.onDeviceAttributesChanged();
         }
     }
 
+    private Collection<MediaDeviceCallback> getCallbacks() {
+        return new CopyOnWriteArrayList<>(mCallbacks);
+    }
+
     /**
      * Callback for notifying device is added, removed and attributes changed.
      */
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 772f6e4..cc2c92b 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -253,6 +253,9 @@
     <!-- Permission required for CTS test - ShortcutManagerUsageTest -->
     <uses-permission android:name="android.permission.ACCESS_SHORTCUTS"/>
 
+     <!-- Permission required for CTS test - UsageStatsTest -->
+    <uses-permission android:name="android.permission.ACCESS_LOCUS_ID_USAGE_STATS"/>
+
     <!-- Permissions required to test ambient display. -->
     <uses-permission android:name="android.permission.READ_DREAM_STATE"/>
     <uses-permission android:name="android.permission.WRITE_DREAM_STATE"/>
diff --git a/packages/SystemUI/res/layout/bubble_overflow_activity.xml b/packages/SystemUI/res/layout/bubble_overflow_activity.xml
index 95f205a..481c4db 100644
--- a/packages/SystemUI/res/layout/bubble_overflow_activity.xml
+++ b/packages/SystemUI/res/layout/bubble_overflow_activity.xml
@@ -14,8 +14,45 @@
   ~ limitations under the License
   -->
 
-<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/bubble_overflow_recycler"
-    android:layout_gravity="center_horizontal"
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/bubble_overflow_container"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"/>
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:layout_gravity="center_horizontal">
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/bubble_overflow_recycler"
+        android:layout_gravity="center_horizontal"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+
+    <LinearLayout
+        android:id="@+id/bubble_overflow_empty_state"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical"
+        android:gravity="center">
+
+        <TextView
+            android:id="@+id/bubble_overflow_empty_title"
+            android:text="@string/bubble_overflow_empty_title"
+            android:fontFamily="@*android:string/config_bodyFontFamilyMedium"
+            android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body2"
+            android:textColor="?android:attr/textColorSecondary"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"/>
+
+        <TextView
+            android:id="@+id/bubble_overflow_empty_subtitle"
+            android:fontFamily="@*android:string/config_bodyFontFamily"
+            android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body2"
+            android:textColor="?android:attr/textColorSecondary"
+            android:text="@string/bubble_overflow_empty_subtitle"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"/>
+    </LinearLayout>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/controls_management_favorites.xml b/packages/SystemUI/res/layout/controls_management_favorites.xml
index a36dd12..62056e6 100644
--- a/packages/SystemUI/res/layout/controls_management_favorites.xml
+++ b/packages/SystemUI/res/layout/controls_management_favorites.xml
@@ -22,14 +22,31 @@
     android:orientation="vertical">
 
     <TextView
+        android:id="@+id/error_message"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/controls_management_list_margin"
+        android:text="@string/controls_favorite_load_error"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:visibility="gone"
+        android:gravity="center_horizontal"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toBottomOf="parent"
+        app:layout_constraintBottom_toTopOf="@id/text_favorites"
+    />
+
+    <TextView
         android:id="@+id/text_favorites"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:text="FAVORITES"
+        android:layout_marginTop="@dimen/controls_management_list_margin"
+        android:text="@string/controls_favorite_header_favorites"
         android:textAppearance="?android:attr/textAppearanceSmall"
+        android:textAllCaps="true"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintBottom_toTopOf="@id/divider1"
-        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/error_message"
         />
 
     <View
@@ -60,8 +77,9 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_marginTop="@dimen/controls_management_list_margin"
-        android:text="ALL"
+        android:text="@string/controls_favorite_header_all"
         android:textAppearance="?android:attr/textAppearanceSmall"
+        android:textAllCaps="true"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintBottom_toTopOf="@id/divider2"
         app:layout_constraintTop_toBottomOf="@id/listFavorites"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d596a5d..ef9e705 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1780,6 +1780,12 @@
     <!-- [CHAR LIMIT=150] Notification Importance title: bubble level summary -->
     <string name="notification_channel_summary_bubble">Keeps your attention with a floating shortcut to this content.</string>
 
+    <!-- [CHAR LIMIT=NONE] Empty overflow title -->
+    <string name="bubble_overflow_empty_title">No recent bubbles</string>
+
+    <!-- [CHAR LIMIT=NONE] Empty overflow subtitle -->
+    <string name="bubble_overflow_empty_subtitle">Recently dismissed bubbles will appear here for easy retrieval.</string>
+
     <!-- Notification: Control panel: Label that displays when the app's notifications cannot be blocked. -->
     <string name="notification_unblockable_desc">These notifications can\'t be modified.</string>
 
@@ -2595,4 +2601,10 @@
     <string name="controls_favorite_default_title">Controls</string>
     <!-- Controls management controls screen subtitle [CHAR LIMIT=NONE] -->
     <string name="controls_favorite_subtitle">Choose controls for quick access</string>
+    <!-- Controls management controls screen favorites header [CHAR LIMIT=50] -->
+    <string name="controls_favorite_header_favorites">Favorites</string>
+    <!-- Controls management controls screen all header [CHAR LIMIT=50] -->
+    <string name="controls_favorite_header_all">All</string>
+    <!-- Controls management controls screen error on load message [CHAR LIMIT=50] -->
+    <string name="controls_favorite_load_error">The list of all controls could not be loaded.</string>
 </resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ConfigurationCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ConfigurationCompat.java
new file mode 100644
index 0000000..d1c77a6
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ConfigurationCompat.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.shared.system;
+
+import android.content.res.Configuration;
+
+/**
+ * Wraps the Configuration to access the window configuration.
+ */
+public class ConfigurationCompat {
+
+    public static int getWindowConfigurationRotation(Configuration c) {
+        return c.windowConfiguration.getRotation();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SysUIToast.java b/packages/SystemUI/src/com/android/systemui/SysUIToast.java
index 0f7f1be..023b74b 100644
--- a/packages/SystemUI/src/com/android/systemui/SysUIToast.java
+++ b/packages/SystemUI/src/com/android/systemui/SysUIToast.java
@@ -19,7 +19,6 @@
 
 import android.annotation.StringRes;
 import android.content.Context;
-import android.view.WindowManager;
 import android.widget.Toast;
 
 public class SysUIToast {
@@ -29,10 +28,7 @@
     }
 
     public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
-        Toast toast = Toast.makeText(context, text, duration);
-        toast.getWindowParams().privateFlags |=
-                WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
-        return toast;
+        return Toast.makeText(context, text, duration);
     }
 
 }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MirrorWindowControl.java b/packages/SystemUI/src/com/android/systemui/accessibility/MirrorWindowControl.java
new file mode 100644
index 0000000..e76a209
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MirrorWindowControl.java
@@ -0,0 +1,197 @@
+/*
+ * 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.accessibility;
+
+import static android.view.WindowManager.LayoutParams;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.util.Log;
+import android.util.MathUtils;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.systemui.R;
+
+/**
+ * Contains a movable control UI to manipulate mirrored window's position, size and scale. The
+ * window type of the UI is {@link LayoutParams#TYPE_APPLICATION_SUB_PANEL} and the window type
+ * of the window token should be {@link LayoutParams#TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY} to
+ * ensure it is above all windows and won't be mirrored. It is not movable to the navigation bar.
+ */
+public abstract class MirrorWindowControl {
+    private static final String TAG = "MirrorWindowControl";
+    private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG) | false;
+
+    /**
+     * A delegate handling a mirrored window's offset.
+     */
+    public interface MirrorWindowDelegate {
+        /**
+         * Moves the window with specified offset.
+         *
+         * @param xOffset the amount in pixels to offset the window in the X coordinate, in current
+         *                display pixels.
+         * @param yOffset the amount in pixels to offset the window in the Y coordinate, in current
+         *                display pixels.
+         */
+        void move(int xOffset, int yOffset);
+    }
+
+    protected final Context mContext;
+    private final Rect mDraggableBound = new Rect();
+    final Point mTmpPoint = new Point();
+
+    @Nullable
+    protected MirrorWindowDelegate mMirrorWindowDelegate;
+    protected View mControlsView;
+    /**
+     * The left top position of the control UI. Initialized when the control UI is visible.
+     *
+     * @see #setDefaultPosition(LayoutParams)
+     */
+    private final Point mControlPosition = new Point();
+    private final WindowManager mWindowManager;
+
+    MirrorWindowControl(Context context) {
+        mContext = context;
+        mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+    }
+
+    public void setWindowDelegate(@Nullable MirrorWindowDelegate windowDelegate) {
+        mMirrorWindowDelegate = windowDelegate;
+    }
+
+    /**
+     * Shows the control UI.
+     *
+     * @param binder the window token of the
+     * {@link LayoutParams#TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY}  window.
+     */
+    public final void showControl(IBinder binder) {
+        if (mControlsView != null) {
+            Log.w(TAG, "control view is visible");
+            return;
+        }
+        final Point  viewSize = mTmpPoint;
+        mControlsView = onCreateView(LayoutInflater.from(mContext), viewSize);
+
+        final LayoutParams lp = new LayoutParams();
+        final int defaultSize = mContext.getResources().getDimensionPixelSize(
+                R.dimen.magnification_controls_size);
+        lp.width = viewSize.x <= 0 ? defaultSize : viewSize.x;
+        lp.height = viewSize.y <= 0 ? defaultSize : viewSize.y;
+        lp.token = binder;
+        setDefaultParams(lp);
+        setDefaultPosition(lp);
+        mWindowManager.addView(mControlsView, lp);
+        updateDraggableBound(lp.width, lp.height);
+    }
+
+    private void setDefaultParams(LayoutParams lp) {
+        lp.gravity = Gravity.TOP | Gravity.LEFT;
+        lp.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL
+                | LayoutParams.FLAG_NOT_FOCUSABLE;
+        lp.type = LayoutParams.TYPE_APPLICATION_SUB_PANEL;
+        lp.format = PixelFormat.RGBA_8888;
+        lp.setTitle(getWindowTitle());
+    }
+
+    private void setDefaultPosition(LayoutParams layoutParams) {
+        final Point displaySize = mTmpPoint;
+        mContext.getDisplay().getSize(displaySize);
+        layoutParams.x = displaySize.x - layoutParams.width;
+        layoutParams.y = displaySize.y - layoutParams.height;
+        mControlPosition.set(layoutParams.x, layoutParams.y);
+    }
+
+    /**
+     * Removes the UI from the scene.
+     */
+    public final void destroyControl() {
+        if (mControlsView != null) {
+            mWindowManager.removeView(mControlsView);
+            mControlsView = null;
+        }
+    }
+
+    /**
+     * Moves the control view with specified offset.
+     *
+     * @param xOffset the amount in pixels to offset the UI in the X coordinate, in current
+     *                display pixels.
+     * @param yOffset the amount in pixels to offset the UI in the Y coordinate, in current
+     *                display pixels.
+     */
+    public void move(int xOffset, int yOffset) {
+        if (mControlsView == null) {
+            Log.w(TAG, "control view is not available yet or destroyed");
+            return;
+        }
+        final Point nextPosition = mTmpPoint;
+        nextPosition.set(mControlPosition.x, mControlPosition.y);
+        mTmpPoint.offset(xOffset, yOffset);
+        setPosition(mTmpPoint);
+    }
+
+    private void setPosition(Point point) {
+        constrainFrameToDraggableBound(point);
+        if (point.equals(mControlPosition)) {
+            return;
+        }
+        mControlPosition.set(point.x, point.y);
+        LayoutParams lp = (LayoutParams) mControlsView.getLayoutParams();
+        lp.x = mControlPosition.x;
+        lp.y = mControlPosition.y;
+        mWindowManager.updateViewLayout(mControlsView, lp);
+    }
+
+    private void constrainFrameToDraggableBound(Point point) {
+        point.x = MathUtils.constrain(point.x, mDraggableBound.left, mDraggableBound.right);
+        point.y = MathUtils.constrain(point.y, mDraggableBound.top, mDraggableBound.bottom);
+    }
+
+    private void updateDraggableBound(int viewWidth, int viewHeight) {
+        final Point size = mTmpPoint;
+        mContext.getDisplay().getSize(size);
+        mDraggableBound.set(0, 0, size.x - viewWidth, size.y - viewHeight);
+        if (DBG) {
+            Log.d(TAG, "updateDraggableBound :" + mDraggableBound);
+        }
+    }
+
+    abstract String getWindowTitle();
+
+    /**
+     * Called when the UI is going to show.
+     *
+     * @param inflater The LayoutInflater object used to inflate the view.
+     * @param viewSize The {@link Point} to specify view's width with {@link Point#x)} and height
+     *                with {@link Point#y)} .The value should be greater than 0, otherwise will
+     *                 fall back to the default size.
+     * @return the View for the control's UI.
+     */
+    @NonNull
+    abstract View onCreateView(@NonNull LayoutInflater inflater, @NonNull Point viewSize);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SimpleMirrorWindowControl.java b/packages/SystemUI/src/com/android/systemui/accessibility/SimpleMirrorWindowControl.java
new file mode 100644
index 0000000..2ba2bb6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SimpleMirrorWindowControl.java
@@ -0,0 +1,221 @@
+/*
+ * 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.accessibility;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.os.Handler;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.systemui.R;
+
+/**
+ * A basic control to move the mirror window.
+ */
+class SimpleMirrorWindowControl extends MirrorWindowControl implements View.OnClickListener,
+        View.OnTouchListener, View.OnLongClickListener {
+
+    private static final String TAG = "SimpleMirrorWindowControl";
+    private static final int MOVE_FRAME_DURATION_MS = 100;
+    private final int mMoveFrameAmountShort;
+    private final int mMoveFrameAmountLong;
+
+    private boolean mIsDragState;
+    private boolean mShouldSetTouchStart;
+
+    @Nullable private MoveWindowTask mMoveWindowTask;
+    private PointF mLastDrag = new PointF();
+    private final Handler mHandler;
+
+    SimpleMirrorWindowControl(Context context, Handler handler) {
+        super(context);
+        mHandler = handler;
+        final Resources resource = context.getResources();
+        mMoveFrameAmountShort = resource.getDimensionPixelSize(
+                R.dimen.magnification_frame_move_short);
+        mMoveFrameAmountLong = resource.getDimensionPixelSize(
+                R.dimen.magnification_frame_move_long);
+    }
+
+    @Override
+    String getWindowTitle() {
+        return mContext.getString(R.string.magnification_controls_title);
+    }
+
+    @Override
+    View onCreateView(LayoutInflater layoutInflater, Point viewSize) {
+        final View view = layoutInflater.inflate(R.layout.magnifier_controllers, null);
+        final View leftControl = view.findViewById(R.id.left_control);
+        final View upControl = view.findViewById(R.id.up_control);
+        final View rightControl = view.findViewById(R.id.right_control);
+        final View bottomControl = view.findViewById(R.id.down_control);
+
+        leftControl.setOnClickListener(this);
+        upControl.setOnClickListener(this);
+        rightControl.setOnClickListener(this);
+        bottomControl.setOnClickListener(this);
+
+        leftControl.setOnLongClickListener(this);
+        upControl.setOnLongClickListener(this);
+        rightControl.setOnLongClickListener(this);
+        bottomControl.setOnLongClickListener(this);
+
+        leftControl.setOnTouchListener(this);
+        upControl.setOnTouchListener(this);
+        rightControl.setOnTouchListener(this);
+        bottomControl.setOnTouchListener(this);
+
+        view.setOnTouchListener(this);
+        view.setOnLongClickListener(this::onViewRootLongClick);
+        return view;
+    }
+
+    private Point findOffset(View v, int moveFrameAmount) {
+        final Point offset = mTmpPoint;
+        offset.set(0, 0);
+        if (v.getId() == R.id.left_control) {
+            mTmpPoint.x = -moveFrameAmount;
+        } else if (v.getId() == R.id.up_control) {
+            mTmpPoint.y = -moveFrameAmount;
+        } else if (v.getId() == R.id.right_control) {
+            mTmpPoint.x = moveFrameAmount;
+        } else if (v.getId() == R.id.down_control) {
+            mTmpPoint.y = moveFrameAmount;
+        } else {
+            Log.w(TAG, "findOffset move is zero ");
+        }
+        return mTmpPoint;
+    }
+
+    @Override
+    public void onClick(View v) {
+        if (mMirrorWindowDelegate != null) {
+            Point offset = findOffset(v, mMoveFrameAmountShort);
+            mMirrorWindowDelegate.move(offset.x, offset.y);
+        }
+    }
+
+    @Override
+    public boolean onTouch(View v, MotionEvent event) {
+        if (handleDragState(event)) {
+            return true;
+        }
+        switch (event.getAction()) {
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+                if (mMoveWindowTask != null) {
+                    mMoveWindowTask.cancel();
+                    mMoveWindowTask = null;
+                }
+                break;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onLongClick(View v) {
+        Point offset = findOffset(v, mMoveFrameAmountLong);
+        mMoveWindowTask = new MoveWindowTask(mMirrorWindowDelegate, mHandler, offset.x, offset.y,
+                MOVE_FRAME_DURATION_MS);
+        mMoveWindowTask.schedule();
+        return true;
+    }
+
+    private boolean onViewRootLongClick(View view) {
+        mIsDragState = true;
+        mShouldSetTouchStart = true;
+        return true;
+    }
+
+    private boolean handleDragState(final MotionEvent event) {
+        switch (event.getAction()) {
+            case MotionEvent.ACTION_MOVE:
+                if (mIsDragState) {
+                    if (mShouldSetTouchStart) {
+                        mLastDrag.set(event.getRawX(), event.getRawY());
+                        mShouldSetTouchStart = false;
+                    }
+                    int xDiff = (int) (event.getRawX() - mLastDrag.x);
+                    int yDiff = (int) (event.getRawY() - mLastDrag.y);
+                    move(xDiff, yDiff);
+                    mLastDrag.set(event.getRawX(), event.getRawY());
+                    return true;
+                }
+                return false;
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+                if (mIsDragState) {
+                    mIsDragState = false;
+                    return true;
+                }
+                return false;
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * A timer task to move the mirror window periodically.
+     */
+    static class MoveWindowTask implements Runnable {
+        private final MirrorWindowDelegate mMirrorWindowDelegate;
+        private final int mXOffset;
+        private final int mYOffset;
+        private final Handler mHandler;
+        /** Time in milliseconds between successive task executions.*/
+        private long mPeriod;
+        private boolean mCancel;
+
+        MoveWindowTask(@NonNull MirrorWindowDelegate windowDelegate, Handler handler, int xOffset,
+                int yOffset, long period) {
+            mMirrorWindowDelegate = windowDelegate;
+            mXOffset = xOffset;
+            mYOffset = yOffset;
+            mHandler = handler;
+            mPeriod = period;
+        }
+
+        @Override
+        public void run() {
+            if (mCancel) {
+                return;
+            }
+            mMirrorWindowDelegate.move(mXOffset, mYOffset);
+            schedule();
+        }
+
+        /**
+         * Schedules the specified task periodically and immediately.
+         */
+        void schedule() {
+            mHandler.postDelayed(this, mPeriod);
+            mCancel = false;
+        }
+
+        void cancel() {
+            mHandler.removeCallbacks(this);
+            mCancel = true;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
index 898cd13..b3ce4a0 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
@@ -86,7 +86,7 @@
 
     private void enableMagnification() {
         if (mWindowMagnificationController == null) {
-            mWindowMagnificationController = new WindowMagnificationController(mContext, mHandler);
+            mWindowMagnificationController = new WindowMagnificationController(mContext, null);
         }
         mWindowMagnificationController.createWindowMagnification();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 581cf7a..7176490 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -18,6 +18,7 @@
 
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.PixelFormat;
@@ -25,7 +26,6 @@
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.Binder;
-import android.os.Handler;
 import android.view.Display;
 import android.view.Gravity;
 import android.view.LayoutInflater;
@@ -44,16 +44,13 @@
 /**
  * Class to handle adding and removing a window magnification.
  */
-public class WindowMagnificationController implements View.OnClickListener,
-        View.OnLongClickListener, View.OnTouchListener, SurfaceHolder.Callback {
+public class WindowMagnificationController implements View.OnTouchListener, SurfaceHolder.Callback,
+        MirrorWindowControl.MirrorWindowDelegate {
     private final int mBorderSize;
-    private final int mMoveFrameAmountShort;
-    private final int mMoveFrameAmountLong;
 
     private final Context mContext;
     private final Point mDisplaySize = new Point();
     private final int mDisplayId;
-    private final Handler mHandler;
     private final Rect mMagnificationFrame = new Rect();
     private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
 
@@ -66,13 +63,6 @@
     // The root of the mirrored content
     private SurfaceControl mMirrorSurface;
 
-    private boolean mIsPressedDown;
-
-    private View mLeftControl;
-    private View mUpControl;
-    private View mRightControl;
-    private View mBottomControl;
-
     private View mDragView;
     private View mLeftDrag;
     private View mTopDrag;
@@ -80,20 +70,18 @@
     private View mBottomDrag;
 
     private final PointF mLastDrag = new PointF();
-    private final Point mMoveWindowOffset = new Point();
 
     private View mMirrorView;
     private SurfaceView mMirrorSurfaceView;
-    private View mControlsView;
     private View mOverlayView;
     // The boundary of magnification frame.
     private final Rect mMagnificationFrameBoundary = new Rect();
 
-    private MoveMirrorRunnable mMoveMirrorRunnable = new MoveMirrorRunnable();
+    @Nullable
+    private MirrorWindowControl mMirrorWindowControl;
 
-    WindowMagnificationController(Context context, Handler handler) {
+    WindowMagnificationController(Context context, MirrorWindowControl mirrorWindowControl) {
         mContext = context;
-        mHandler = handler;
         Display display = mContext.getDisplay();
         display.getRealSize(mDisplaySize);
         mDisplayId = mContext.getDisplayId();
@@ -102,10 +90,12 @@
 
         Resources r = context.getResources();
         mBorderSize = (int) r.getDimension(R.dimen.magnification_border_size);
-        mMoveFrameAmountShort = (int) r.getDimension(R.dimen.magnification_frame_move_short);
-        mMoveFrameAmountLong = (int) r.getDimension(R.dimen.magnification_frame_move_long);
 
         mScale = r.getInteger(R.integer.magnification_default_scale);
+        mMirrorWindowControl = mirrorWindowControl;
+        if (mMirrorWindowControl != null) {
+            mMirrorWindowControl.setWindowDelegate(this);
+        }
     }
 
     /**
@@ -176,9 +166,8 @@
             mMirrorView = null;
         }
 
-        if (mControlsView != null) {
-            mWm.removeView(mControlsView);
-            mControlsView = null;
+        if (mMirrorWindowControl != null) {
+            mMirrorWindowControl.destroyControl();
         }
     }
 
@@ -238,40 +227,9 @@
     }
 
     private void createControls() {
-        int controlsSize = (int) mContext.getResources().getDimension(
-                R.dimen.magnification_controls_size);
-
-        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(controlsSize, controlsSize,
-                WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL,
-                WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
-                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
-                PixelFormat.RGBA_8888);
-        lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
-        lp.token = mOverlayView.getWindowToken();
-        lp.setTitle(mContext.getString(R.string.magnification_controls_title));
-
-        mControlsView = LayoutInflater.from(mContext).inflate(R.layout.magnifier_controllers, null);
-        mWm.addView(mControlsView, lp);
-
-        mLeftControl = mControlsView.findViewById(R.id.left_control);
-        mUpControl = mControlsView.findViewById(R.id.up_control);
-        mRightControl = mControlsView.findViewById(R.id.right_control);
-        mBottomControl = mControlsView.findViewById(R.id.down_control);
-
-        mLeftControl.setOnClickListener(this);
-        mUpControl.setOnClickListener(this);
-        mRightControl.setOnClickListener(this);
-        mBottomControl.setOnClickListener(this);
-
-        mLeftControl.setOnLongClickListener(this);
-        mUpControl.setOnLongClickListener(this);
-        mRightControl.setOnLongClickListener(this);
-        mBottomControl.setOnLongClickListener(this);
-
-        mLeftControl.setOnTouchListener(this);
-        mUpControl.setOnTouchListener(this);
-        mRightControl.setOnTouchListener(this);
-        mBottomControl.setOnTouchListener(this);
+        if (mMirrorWindowControl != null) {
+            mMirrorWindowControl.showControl(mOverlayView.getWindowToken());
+        }
     }
 
     private void setInitialStartBounds() {
@@ -331,40 +289,14 @@
     }
 
     @Override
-    public void onClick(View v) {
-        setMoveOffset(v, mMoveFrameAmountShort);
-        moveMirrorWindow(mMoveWindowOffset.x, mMoveWindowOffset.y);
-    }
-
-    @Override
-    public boolean onLongClick(View v) {
-        mIsPressedDown = true;
-        setMoveOffset(v, mMoveFrameAmountLong);
-        mHandler.post(mMoveMirrorRunnable);
-        return true;
-    }
-
-    @Override
     public boolean onTouch(View v, MotionEvent event) {
-        if (v == mLeftControl || v == mUpControl || v == mRightControl || v == mBottomControl) {
-            return handleControlTouchEvent(event);
-        } else if (v == mDragView || v == mLeftDrag || v == mTopDrag || v == mRightDrag
+        if (v == mDragView || v == mLeftDrag || v == mTopDrag || v == mRightDrag
                 || v == mBottomDrag) {
             return handleDragTouchEvent(event);
         }
         return false;
     }
 
-    private boolean handleControlTouchEvent(MotionEvent event) {
-        switch (event.getAction()) {
-            case MotionEvent.ACTION_UP:
-            case MotionEvent.ACTION_CANCEL:
-                mIsPressedDown = false;
-                break;
-        }
-        return false;
-    }
-
     private boolean handleDragTouchEvent(MotionEvent event) {
         switch (event.getAction()) {
             case MotionEvent.ACTION_DOWN:
@@ -380,20 +312,6 @@
         return false;
     }
 
-    private void setMoveOffset(View v, int moveFrameAmount) {
-        mMoveWindowOffset.set(0, 0);
-
-        if (v == mLeftControl) {
-            mMoveWindowOffset.x = -moveFrameAmount;
-        } else if (v == mUpControl) {
-            mMoveWindowOffset.y = -moveFrameAmount;
-        } else if (v == mRightControl) {
-            mMoveWindowOffset.x = moveFrameAmount;
-        } else if (v == mBottomControl) {
-            mMoveWindowOffset.y = moveFrameAmount;
-        }
-    }
-
     private void moveMirrorWindow(int xOffset, int yOffset) {
         if (updateMagnificationFramePosition(xOffset, yOffset)) {
             modifyWindowMagnification(mTransaction);
@@ -461,6 +379,7 @@
         }
         return false;
     }
+
     @Override
     public void surfaceCreated(SurfaceHolder holder) {
         createMirror();
@@ -474,13 +393,13 @@
     public void surfaceDestroyed(SurfaceHolder holder) {
     }
 
-    class MoveMirrorRunnable implements Runnable {
-        @Override
-        public void run() {
-            if (mIsPressedDown) {
-                moveMirrorWindow(mMoveWindowOffset.x, mMoveWindowOffset.y);
-                mHandler.postDelayed(mMoveMirrorRunnable, 100);
-            }
+    @Override
+    public void move(int xOffset, int yOffset) {
+        if (mMirrorSurfaceView == null) {
+            return;
         }
+        mMagnificationFrame.offset(xOffset, yOffset);
+        modifyWindowMagnification(mTransaction);
+        mTransaction.apply();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index 45705b7..1e39954 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -226,6 +226,10 @@
         mIconView.update(this);
     }
 
+    void setInflated(boolean inflated) {
+        mInflated = inflated;
+    }
+
     /**
      * Set visibility of bubble in the expanded state.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 05838ab..762e5f2 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -749,7 +749,8 @@
     }
 
     void promoteBubbleFromOverflow(Bubble bubble) {
-        mBubbleData.promoteBubbleFromOverflow(bubble);
+        bubble.setInflateSynchronously(mInflateSynchronously);
+        mBubbleData.promoteBubbleFromOverflow(bubble, mStackView, mBubbleIconFactory);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index 673121f..8a5aad8 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -199,16 +199,21 @@
         dispatchPendingChanges();
     }
 
-    public void promoteBubbleFromOverflow(Bubble bubble) {
+    public void promoteBubbleFromOverflow(Bubble bubble, BubbleStackView stack,
+            BubbleIconFactory factory) {
         if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "promoteBubbleFromOverflow: " + bubble);
         }
-        mOverflowBubbles.remove(bubble);
-        doAdd(bubble);
-        setSelectedBubbleInternal(bubble);
+
         // Preserve new order for next repack, which sorts by last updated time.
         bubble.markUpdatedAt(mTimeSource.currentTimeMillis());
-        trim();
+        setSelectedBubbleInternal(bubble);
+        mOverflowBubbles.remove(bubble);
+
+        bubble.inflate(
+                b -> notificationEntryUpdated(bubble, /* suppressFlyout */
+                        false, /* showInShade */ true),
+                mContext, stack, factory);
         dispatchPendingChanges();
     }
 
@@ -445,6 +450,10 @@
             mOverflowBubbles.add(0, bubbleToRemove);
             if (mOverflowBubbles.size() == mMaxOverflowBubbles + 1) {
                 // Remove oldest bubble.
+                if (DEBUG_BUBBLE_DATA) {
+                    Log.d(TAG, "Overflow full. Remove bubble: " + mOverflowBubbles.get(
+                            mOverflowBubbles.size() - 1));
+                }
                 mOverflowBubbles.remove(mOverflowBubbles.size() - 1);
             }
         }
@@ -511,7 +520,7 @@
         if (Objects.equals(bubble, mSelectedBubble)) {
             return;
         }
-        if (bubble != null && !mBubbles.contains(bubble)) {
+        if (bubble != null && !mBubbles.contains(bubble) && !mOverflowBubbles.contains(bubble)) {
             Log.e(TAG, "Cannot select bubble which doesn't exist!"
                     + " (" + bubble + ") bubbles=" + mBubbles);
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index 0d5261d..fe191f4 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -294,7 +294,8 @@
         ta.recycle();
 
         mPointerDrawable.setTint(bgColor);
-        if (ScreenDecorationsUtils.supportsRoundedCornersOnWindows(mContext.getResources())) {
+        if (mActivityView != null && ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
+                mContext.getResources())) {
             mActivityView.setCornerRadius(cornerRadius);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
index 2d55a1d..f3cfa83 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
@@ -26,7 +26,10 @@
 import android.os.Bundle;
 import android.util.Log;
 import android.view.LayoutInflater;
+import android.view.View;
 import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
 
 import androidx.recyclerview.widget.GridLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
@@ -46,6 +49,7 @@
 public class BubbleOverflowActivity extends Activity {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleOverflowActivity" : TAG_BUBBLES;
 
+    private LinearLayout mEmptyState;
     private BubbleController mBubbleController;
     private BubbleOverflowAdapter mAdapter;
     private RecyclerView mRecyclerView;
@@ -64,6 +68,7 @@
         setBackgroundColor();
 
         mMaxBubbles = getResources().getInteger(R.integer.bubbles_max_rendered);
+        mEmptyState = findViewById(R.id.bubble_overflow_empty_state);
         mRecyclerView = findViewById(R.id.bubble_overflow_recycler);
         mRecyclerView.setLayoutManager(
                 new GridLayoutManager(getApplicationContext(),
@@ -73,9 +78,9 @@
                 mBubbleController::promoteBubbleFromOverflow);
         mRecyclerView.setAdapter(mAdapter);
 
-        updateData(mBubbleController.getOverflowBubbles());
+        onDataChanged(mBubbleController.getOverflowBubbles());
         mBubbleController.setOverflowCallback(() -> {
-            updateData(mBubbleController.getOverflowBubbles());
+            onDataChanged(mBubbleController.getOverflowBubbles());
         });
     }
 
@@ -87,7 +92,7 @@
         findViewById(android.R.id.content).setBackgroundColor(bgColor);
     }
 
-    void updateData(List<Bubble> bubbles) {
+    void onDataChanged(List<Bubble> bubbles) {
         mOverflowBubbles.clear();
         if (bubbles.size() > mMaxBubbles) {
             mOverflowBubbles.addAll(bubbles.subList(mMaxBubbles, bubbles.size()));
@@ -96,6 +101,12 @@
         }
         mAdapter.notifyDataSetChanged();
 
+        if (mOverflowBubbles.isEmpty()) {
+            mEmptyState.setVisibility(View.VISIBLE);
+        } else {
+            mEmptyState.setVisibility(View.GONE);
+        }
+
         if (DEBUG_OVERFLOW) {
             Log.d(TAG, "Updated overflow bubbles:\n" + BubbleDebugConfig.formatBubblesString(
                     mOverflowBubbles, /*selected*/ null));
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index bce172b..cff78cf 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -528,6 +528,12 @@
         mBubbleContainer.addView(mOverflowBtn, 0,
                 new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
 
+        setOverflowBtnTheme();
+        mOverflowBtn.setVisibility(GONE);
+    }
+
+    // TODO(b/149146374) Propagate theme change to bubbles in overflow.
+    private void setOverflowBtnTheme() {
         TypedArray ta = mContext.obtainStyledAttributes(
                 new int[]{android.R.attr.colorBackgroundFloating});
         int bgColor = ta.getColor(0, Color.WHITE /* default */);
@@ -537,8 +543,6 @@
         ColorDrawable bg = new ColorDrawable(bgColor);
         AdaptiveIconDrawable adaptiveIcon = new AdaptiveIconDrawable(bg, fg);
         mOverflowBtn.setImageDrawable(adaptiveIcon);
-
-        mOverflowBtn.setVisibility(GONE);
     }
 
     void showExpandedViewContents(int displayId) {
@@ -568,6 +572,9 @@
      */
     public void onThemeChanged() {
         setUpFlyout();
+        if (BubbleExperimentConfig.allowBubbleOverflow(mContext)) {
+            setOverflowBtnTheme();
+        }
     }
 
     /** Respond to the phone being rotated by repositioning the stack and hiding any flyouts. */
@@ -795,6 +802,7 @@
         if (removedIndex >= 0) {
             mBubbleContainer.removeViewAt(removedIndex);
             bubble.cleanupExpandedState();
+            bubble.setInflated(false);
             logBubbleEvent(bubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED);
         } else {
             Log.d(TAG, "was asked to remove Bubble, but didn't find the view! " + bubble);
diff --git a/packages/SystemUI/src/com/android/systemui/controls/UserAwareController.kt b/packages/SystemUI/src/com/android/systemui/controls/UserAwareController.kt
index 4f39f22..d2776d2 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/UserAwareController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/UserAwareController.kt
@@ -18,8 +18,11 @@
 
 import android.os.UserHandle
 
+/**
+ * An interface for controllers that keep track of the current user and can be notified of user
+ * changes.
+ */
 interface UserAwareController {
-
     fun changeUser(newUser: UserHandle) {}
     val currentUserId: Int
 }
\ 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
index b6cca3f..f624120 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlInfo.kt
@@ -22,6 +22,15 @@
 
 /**
  * Stores basic information about a [Control] to persist and keep track of favorites.
+ *
+ * The identifier of this [Control] is the combination of [component] and [controlId]. The other
+ * two fields are there for persistence. In this way, basic information can be shown to the user
+ * before the service has to report on the status.
+ *
+ * @property component the name of the component that provides the [Control].
+ * @property controlId unique (for the given [component]) identifier for this [Control].
+ * @property controlTitle last title reported for this [Control].
+ * @property deviceType last reported type for this [Control].
  */
 data class ControlInfo(
     val component: ComponentName,
@@ -33,6 +42,14 @@
     companion object {
         private const val TAG = "ControlInfo"
         private const val SEPARATOR = ":"
+
+        /**
+         * Creates a [ControlInfo] from a [SEPARATOR] separated list of fields.
+         *
+         * @param separator fields of a [ControlInfo] separated by [SEPARATOR]
+         * @return a [ControlInfo] or `null` if there was an error.
+         * @see [ControlInfo.toString]
+         */
         fun createFromString(string: String): ControlInfo? {
             val parts = string.split(SEPARATOR)
             val component = ComponentName.unflattenFromString(parts[0])
@@ -53,6 +70,12 @@
                     if (DeviceTypes.validDeviceType(type)) type else DeviceTypes.TYPE_UNKNOWN)
         }
     }
+
+    /**
+     * Returns a [String] representation of the fields separated using [SEPARATOR].
+     *
+     * @return a [String] representation of `this`
+     */
     override fun toString(): String {
         return component.flattenToString() +
                 "$SEPARATOR$controlId$SEPARATOR$controlTitle$SEPARATOR$deviceType"
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt
index 12c3ce9..7fae6a3 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt
@@ -18,13 +18,72 @@
 
 import android.content.ComponentName
 import android.service.controls.Control
+import android.service.controls.ControlsProviderService
 import android.service.controls.actions.ControlAction
 import com.android.systemui.controls.UserAwareController
+import java.util.function.Consumer
 
+/**
+ * Controller for keeping track of any [ControlsProviderService] that needs to be bound.
+ *
+ * This controller serves as an interface between [ControlsController] and the services.
+ *
+ * This controller being a [UserAwareController] means that all binding and requests will be
+ * performed on services bound as the current user.
+ */
 interface ControlsBindingController : UserAwareController {
-    fun bindAndLoad(component: ComponentName, callback: (List<Control>) -> Unit)
+
+    /**
+     * Request bind to a service and load all controls.
+     *
+     * @param component The [ComponentName] of the service to bind
+     * @param callback a callback to return the loaded controls to (or an error).
+     */
+    fun bindAndLoad(component: ComponentName, callback: LoadCallback)
+
+    /**
+     * Request to bind to the given services.
+     *
+     * @param components a list of [ComponentName] of the services to bind
+     */
     fun bindServices(components: List<ComponentName>)
+
+    /**
+     * Send a subscribe message to retrieve status of a set of controls.
+     *
+     * The controls passed do not have to belong to a single [ControlsProviderService]. The
+     * corresponding service [ComponentName] is associated with each control.
+     *
+     * @param controls a list of controls with corresponding [ComponentName] to request status
+     *                 update
+     */
     fun subscribe(controls: List<ControlInfo>)
+
+    /**
+     * Send an action performed on a [Control].
+     *
+     * @param controlInfo information about the actioned control, including the [ComponentName]
+     * @param action the action performed on the control
+     */
     fun action(controlInfo: ControlInfo, action: ControlAction)
+
+    /**
+     * Unsubscribe from all services to stop status updates.
+     */
     fun unsubscribe()
+
+    /**
+     * Consumer for load calls.
+     *
+     * Supports also sending error messages.
+     */
+    interface LoadCallback : Consumer<List<Control>> {
+
+        /**
+         * Indicates an error loading.
+         *
+         * @message an error message.
+         */
+        fun error(message: String)
+    }
 }
\ 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
index 0a2a925..87bdfa8 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
@@ -125,7 +125,10 @@
         }
     }
 
-    override fun bindAndLoad(component: ComponentName, callback: (List<Control>) -> Unit) {
+    override fun bindAndLoad(
+        component: ComponentName,
+        callback: ControlsBindingController.LoadCallback
+    ) {
         val provider = retrieveLifecycleManager(component)
         provider.maybeBindAndLoad(callback)
     }
@@ -230,7 +233,7 @@
                     return
                 }
             }
-            provider.lastLoadCallback?.invoke(list) ?: run {
+            provider.lastLoadCallback?.accept(list) ?: run {
                 Log.w(TAG, "Null callback")
             }
             provider.unbindService()
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
index fce5041..4b89fd4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
@@ -18,33 +18,194 @@
 
 import android.content.ComponentName
 import android.service.controls.Control
+import android.service.controls.ControlsProviderService
 import android.service.controls.actions.ControlAction
 import com.android.systemui.controls.ControlStatus
 import com.android.systemui.controls.UserAwareController
+import com.android.systemui.controls.management.ControlsFavoritingActivity
+import com.android.systemui.controls.ui.ControlsUiController
+import java.util.function.Consumer
 
+/**
+ * Controller to handle communication between different parts of the controls system.
+ *
+ * This controller is in charge of:
+ *  * Keeping track of favorites
+ *  * Determining and keeping track of whether controls are enabled
+ *  * Listening for user change and propagating that message in the system
+ *  * Communicate between the UI and the [ControlsBindingController]
+ *
+ *  This controller being a [UserAwareController] means that all operations will be conducted on
+ *  information for the current user only.
+ */
 interface ControlsController : UserAwareController {
+
+    /**
+     * Whether the controls system is available for the current user.
+     */
     val available: Boolean
 
-    fun getFavoriteControls(): List<ControlInfo>
+    // SERVICE COMMUNICATION
+
+    /**
+     * Load all available [Control] for a given service.
+     *
+     * @param componentName the [ComponentName] of the [ControlsProviderService] to load from
+     * @param dataCallback a callback in which to retrieve the result.
+     */
     fun loadForComponent(
         componentName: ComponentName,
-        callback: (List<ControlStatus>, List<String>) -> Unit
+        dataCallback: Consumer<LoadData>
     )
 
+    /**
+     * Request to subscribe for all favorite controls.
+     *
+     * @see [ControlsBindingController.subscribe]
+     */
     fun subscribeToFavorites()
-    fun changeFavoriteStatus(controlInfo: ControlInfo, state: Boolean)
-    fun replaceFavoritesForComponent(componentName: ComponentName, favorites: List<ControlInfo>)
 
-    fun getFavoritesForComponent(componentName: ComponentName): List<ControlInfo>
-    fun countFavoritesForComponent(componentName: ComponentName): Int
-
+    /**
+     * Request to unsubscribe to all providers.
+     *
+     * @see [ControlsBindingController.unsubscribe]
+     */
     fun unsubscribe()
+
+    /**
+     * Notify a [ControlsProviderService] that an action has been performed on a [Control].
+     *
+     * @param controlInfo information of the [Control] receiving the action
+     * @param action action performed on the [Control]
+     * @see [ControlsBindingController.action]
+     */
     fun action(controlInfo: ControlInfo, action: ControlAction)
+
+    /**
+     * Refresh the status of a [Control] with information provided from the service.
+     *
+     * @param componentName the name of the service that provides the [Control]
+     * @param control a stateful [Control] with updated information
+     * @see [ControlsUiController.onRefreshState]
+     */
     fun refreshStatus(componentName: ComponentName, control: Control)
+
+    /**
+     * Indicate the result of a [ControlAction] performed on a [Control].
+     *
+     * @param componentName the name of the service that provides the [Control]
+     * @param controlId the id of the [Control] the actioned was performed on
+     * @param response the result of the action.
+     * @see [ControlsUiController.onActionResponse]
+     */
     fun onActionResponse(
         componentName: ComponentName,
         controlId: String,
         @ControlAction.ResponseResult response: Int
     )
+
+    // FAVORITE MANAGEMENT
+
+    /**
+     * Get a list of all favorite controls.
+     *
+     * @return a list of [ControlInfo] with persistent information about the controls, including
+     *         their corresponding [ComponentName].
+     */
+    fun getFavoriteControls(): List<ControlInfo>
+
+    /**
+     * Get all the favorites for a given component.
+     *
+     * @param componentName the name of the component of the [ControlsProviderService] with
+     *                      which to filter the favorites.
+     * @return a list of the favorite controls for the given service. All the elements of the list
+     *         will have the same [ControlInfo.component] matching the one requested.
+     */
+    fun getFavoritesForComponent(componentName: ComponentName): List<ControlInfo>
+
+    /**
+     * Replaces the favorites for the given component.
+     *
+     * Calling this method will eliminate the previous selection of favorites and replace it with a
+     * new one.
+     *
+     * @param componentName The name of the component for the [ControlsProviderService]
+     * @param favorites a list of [ControlInfo] to replace the previous favorites.
+     */
+    fun replaceFavoritesForComponent(componentName: ComponentName, favorites: List<ControlInfo>)
+
+    /**
+     * Change the favorite status of a single [Control].
+     *
+     * If the control is added to favorites, it will be added to the end of the list for that
+     * particular component. Matching for removing the control will be done based on
+     * [ControlInfo.component] and [ControlInfo.controlId].
+     *
+     * Trying to add an already favorite control or trying to remove one that is not a favorite is
+     * a no-op.
+     *
+     * @param controlInfo persistent information about the [Control].
+     * @param state `true` to add to favorites and `false` to remove.
+     */
+    fun changeFavoriteStatus(controlInfo: ControlInfo, state: Boolean)
+
+    /**
+     * Return the number of favorites for a given component.
+     *
+     * This call returns the same as `getFavoritesForComponent(componentName).size`.
+     *
+     * @param componentName the name of the component
+     * @return the number of current favorites for the given component
+     */
+    fun countFavoritesForComponent(componentName: ComponentName): Int
+
+    /**
+     * Clears the list of all favorites.
+     *
+     * To clear the list of favorites for a given service, call [replaceFavoritesForComponent] with
+     * an empty list.
+     */
     fun clearFavorites()
+
+    /**
+     * Interface for structure to pass data to [ControlsFavoritingActivity].
+     */
+    interface LoadData {
+        /**
+         * All of the available controls for the loaded [ControlsProviderService].
+         *
+         * This will indicate if they are currently a favorite and whether they were removed (a
+         * favorite but not retrieved on load).
+         */
+        val allControls: List<ControlStatus>
+
+        /**
+         * Ordered list of ids of favorite controls.
+         */
+        val favoritesIds: List<String>
+
+        /**
+         * Whether there was an error in loading.
+         *
+         * In this case, [allControls] will only contain those that were favorited and will not be
+         * marked as removed.
+         */
+        val errorOnLoad: Boolean
+    }
+}
+
+/**
+ * Creates a basic implementation of a [LoadData].
+ */
+fun createLoadDataObject(
+    allControls: List<ControlStatus>,
+    favorites: List<String>,
+    error: Boolean = false
+): ControlsController.LoadData {
+    return object : ControlsController.LoadData {
+        override val allControls = allControls
+        override val favoritesIds = favorites
+        override val errorOnLoad = error
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index e611197..50ad515 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -38,7 +38,6 @@
 import com.android.systemui.Dumpable
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.controls.ControlStatus
-import com.android.systemui.controls.management.ControlsFavoritingActivity
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.ui.ControlsUiController
 import com.android.systemui.dagger.qualifiers.Background
@@ -47,6 +46,7 @@
 import java.io.PrintWriter
 import java.util.Optional
 import java.util.concurrent.TimeUnit
+import java.util.function.Consumer
 import javax.inject.Inject
 import javax.inject.Singleton
 
@@ -188,47 +188,74 @@
 
     override fun loadForComponent(
         componentName: ComponentName,
-        callback: (List<ControlStatus>, List<String>) -> Unit
+        dataCallback: Consumer<ControlsController.LoadData>
     ) {
         if (!confirmAvailability()) {
             if (userChanging) {
                 // Try again later, userChanging should not last forever. If so, we have bigger
                 // problems
                 executor.executeDelayed(
-                        { loadForComponent(componentName, callback) },
+                        { loadForComponent(componentName, dataCallback) },
                         USER_CHANGE_RETRY_DELAY,
                         TimeUnit.MILLISECONDS
                 )
             } else {
-                callback(emptyList(), emptyList())
+                dataCallback.accept(createLoadDataObject(emptyList(), emptyList(), true))
             }
             return
         }
-        bindingController.bindAndLoad(componentName) {
-            synchronized(currentFavorites) {
-                val favoritesForComponentKeys: List<String> =
-                        currentFavorites.getValue(componentName).map { it.controlId }
-                val changed = updateFavoritesLocked(componentName, it, favoritesForComponentKeys)
-                if (changed) {
-                    persistenceWrapper.storeFavorites(favoritesAsListLocked())
+        bindingController.bindAndLoad(
+                componentName,
+                object : ControlsBindingController.LoadCallback {
+                    override fun accept(controls: List<Control>) {
+                        val loadData = synchronized(currentFavorites) {
+                            val favoritesForComponentKeys: List<String> =
+                                    currentFavorites.getValue(componentName).map { it.controlId }
+                            val changed = updateFavoritesLocked(componentName, controls,
+                                    favoritesForComponentKeys)
+                            if (changed) {
+                                persistenceWrapper.storeFavorites(favoritesAsListLocked())
+                            }
+                            val removed = findRemovedLocked(favoritesForComponentKeys.toSet(),
+                                    controls)
+                            val controlsWithFavorite = controls.map {
+                                ControlStatus(it, it.controlId in favoritesForComponentKeys)
+                            }
+                            createLoadDataObject(
+                                    currentFavorites.getValue(componentName)
+                                            .filter { it.controlId in removed }
+                                            .map { createRemovedStatus(it) } +
+                                            controlsWithFavorite,
+                                    favoritesForComponentKeys
+                            )
+                        }
+                        dataCallback.accept(loadData)
+                    }
+
+                    override fun error(message: String) {
+                        val loadData = synchronized(currentFavorites) {
+                            val favoritesForComponent = currentFavorites.getValue(componentName)
+                            val favoritesForComponentKeys = favoritesForComponent
+                                    .map { it.controlId }
+                            createLoadDataObject(
+                                    favoritesForComponent.map { createRemovedStatus(it, false) },
+                                    favoritesForComponentKeys,
+                                    true
+                            )
+                        }
+                        dataCallback.accept(loadData)
+                    }
                 }
-                val removed = findRemovedLocked(favoritesForComponentKeys.toSet(), it)
-                val controlsWithFavorite =
-                        it.map { ControlStatus(it, it.controlId in favoritesForComponentKeys) }
-                callback(
-                        currentFavorites.getValue(componentName)
-                                .filter { it.controlId in removed }
-                                .map(::createRemovedStatus) + controlsWithFavorite,
-                        favoritesForComponentKeys
-                )
-            }
-        }
+        )
     }
 
-    private fun createRemovedStatus(controlInfo: ControlInfo): ControlStatus {
-        val intent = Intent(context, ControlsFavoritingActivity::class.java).apply {
-            putExtra(Intent.EXTRA_COMPONENT_NAME, controlInfo.component)
-            flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP
+    private fun createRemovedStatus(
+        controlInfo: ControlInfo,
+        setRemoved: Boolean = true
+    ): ControlStatus {
+        val intent = Intent(Intent.ACTION_MAIN).apply {
+            addCategory(Intent.CATEGORY_LAUNCHER)
+            this.`package` = controlInfo.component.packageName
         }
         val pendingIntent = PendingIntent.getActivity(context,
                 controlInfo.component.hashCode(),
@@ -238,7 +265,7 @@
                 .setTitle(controlInfo.controlTitle)
                 .setDeviceType(controlInfo.deviceType)
                 .build()
-        return ControlStatus(control, true, true)
+        return ControlStatus(control, true, setRemoved)
     }
 
     @GuardedBy("currentFavorites")
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt
index 7d1df14..d7f3c73 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt
@@ -19,9 +19,7 @@
 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
@@ -29,10 +27,18 @@
 import java.io.FileInputStream
 import java.io.FileNotFoundException
 import java.io.IOException
+import java.util.concurrent.Executor
 
+/**
+ * Manages persistence of favorite controls.
+ *
+ * This class uses an [AtomicFile] to serialize the favorite controls to an xml.
+ * @property file a file location for storing/reading the favorites.
+ * @property executor an executor in which to execute storing the favorites.
+ */
 class ControlsFavoritePersistenceWrapper(
     private var file: File,
-    private var executor: DelayableExecutor
+    private val executor: Executor
 ) {
 
     companion object {
@@ -46,10 +52,20 @@
         private const val TAG_TYPE = "type"
     }
 
+    /**
+     * Change the file location for storing/reading the favorites
+     *
+     * @param fileName new location
+     */
     fun changeFile(fileName: File) {
         file = fileName
     }
 
+    /**
+     * Stores the list of favorites in the corresponding file.
+     *
+     * @param list a list of favorite controls. The list will be stored in the same order.
+     */
     fun storeFavorites(list: List<ControlInfo>) {
         executor.execute {
             Log.d(TAG, "Saving data to file: $file")
@@ -87,6 +103,12 @@
         }
     }
 
+    /**
+     * Stores the list of favorites in the corresponding file.
+     *
+     * @return a list of stored favorite controls. Return an empty list if the file is not found
+     * @throws [IllegalStateException] if there is an error while reading the file
+     */
     fun readFavorites(): List<ControlInfo> {
         if (!file.exists()) {
             Log.d(TAG, "No favorites, returning empty list")
@@ -95,7 +117,7 @@
         val reader = try {
             FileInputStream(file)
         } catch (fnfe: FileNotFoundException) {
-            Slog.i(TAG, "No file found")
+            Log.i(TAG, "No file found")
             return emptyList()
         }
         try {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
index b4bd82c..e09d20b 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
@@ -25,7 +25,7 @@
 import android.os.IBinder
 import android.os.RemoteException
 import android.os.UserHandle
-import android.service.controls.Control
+import android.service.controls.ControlsProviderService
 import android.service.controls.ControlsProviderService.CALLBACK_BUNDLE
 import android.service.controls.ControlsProviderService.CALLBACK_TOKEN
 import android.service.controls.IControlsActionCallback
@@ -40,7 +40,23 @@
 import com.android.systemui.util.concurrency.DelayableExecutor
 import java.util.concurrent.TimeUnit
 
-typealias LoadCallback = (List<Control>) -> Unit
+/**
+ * Manager for the lifecycle of the connection to a given [ControlsProviderService].
+ *
+ * This class handles binding and unbinding and requests to the service. The class will queue
+ * requests until the service is connected and dispatch them then.
+ *
+ * @property context A SystemUI context for binding to the services
+ * @property executor A delayable executor for posting timeouts
+ * @property loadCallbackService a callback interface to hand the remote service for loading
+ *                               controls
+ * @property actionCallbackService a callback interface to hand the remote service for sending
+ *                                 action responses
+ * @property subscriberService an "subscriber" interface for requesting and accepting updates for
+ *                             controls from the service.
+ * @property user the user for whose this service should be bound.
+ * @property componentName the name of the component for the service.
+ */
 class ControlsProviderLifecycleManager(
     private val context: Context,
     private val executor: DelayableExecutor,
@@ -51,7 +67,7 @@
     val componentName: ComponentName
 ) : IBinder.DeathRecipient {
 
-    var lastLoadCallback: LoadCallback? = null
+    var lastLoadCallback: ControlsBindingController.LoadCallback? = null
         private set
     val token: IBinder = Binder()
     @GuardedBy("subscriptions")
@@ -187,7 +203,7 @@
         }
     }
 
-    private fun invokeOrQueue(f: () -> Unit, msg: Message) {
+    private inline fun invokeOrQueue(f: () -> Unit, msg: Message) {
         wrapper?.run {
             f()
         } ?: run {
@@ -196,18 +212,37 @@
         }
     }
 
-    fun maybeBindAndLoad(callback: LoadCallback) {
+    /**
+     * Request a call to [ControlsProviderService.loadAvailableControls].
+     *
+     * If the service is not bound, the call will be queued and the service will be bound first.
+     * The service will be bound after the controls are returned or the call times out.
+     *
+     * @param callback a callback in which to return the result back. If the call times out
+     *                 [ControlsBindingController.LoadCallback.error] will be called instead.
+     */
+    fun maybeBindAndLoad(callback: ControlsBindingController.LoadCallback) {
         unqueueMessage(Message.Unbind)
         lastLoadCallback = callback
         onLoadCanceller = executor.executeDelayed({
-            // Didn't receive a response in time, log and send back empty list
+            // Didn't receive a response in time, log and send back error
             Log.d(TAG, "Timeout waiting onLoad for $componentName")
-            loadCallbackService.accept(token, emptyList())
+            callback.error("Timeout waiting onLoad")
+            // Don't accept load callbacks after this
+            lastLoadCallback = null
+            unbindService()
         }, LOAD_TIMEOUT, TimeUnit.MILLISECONDS)
 
         invokeOrQueue(::load, Message.Load)
     }
 
+    /**
+     * Request a subscription to the [Publisher] returned by [ControlsProviderService.publisherFor]
+     *
+     * If the service is not bound, the call will be queued and the service will be bound first.
+     *
+     * @param controlIds a list of the ids of controls to send status back.
+     */
     fun maybeBindAndSubscribe(controlIds: List<String>) {
         invokeOrQueue({ subscribe(controlIds) }, Message.Subscribe(controlIds))
     }
@@ -222,6 +257,14 @@
         }
     }
 
+    /**
+     * Request a call to [ControlsProviderService.performControlAction].
+     *
+     * If the service is not bound, the call will be queued and the service will be bound first.
+     *
+     * @param controlId the id of the [Control] the action is performed on
+     * @param action the action performed
+     */
     fun maybeBindAndSendAction(controlId: String, action: ControlAction) {
         invokeOrQueue({ action(controlId, action) }, Message.Action(controlId, action))
     }
@@ -236,6 +279,12 @@
         }
     }
 
+    /**
+     * Starts the subscription to the [ControlsProviderService] and requests status of controls.
+     *
+     * @param subscription the subscriber to use to request controls
+     * @see maybeBindAndLoad
+     */
     fun startSubscription(subscription: IControlsSubscription) {
         synchronized(subscriptions) {
             subscriptions.add(subscription)
@@ -243,6 +292,9 @@
         wrapper?.request(subscription, MAX_CONTROLS_REQUEST)
     }
 
+    /**
+     * Unsubscribe from this service, cancelling all status requests.
+     */
     fun unsubscribe() {
         if (DEBUG) {
             Log.d(TAG, "unsubscribe $componentName")
@@ -260,11 +312,17 @@
         }
     }
 
+    /**
+     * Request bind to the service.
+     */
     fun bindService() {
         unqueueMessage(Message.Unbind)
         bindService(true)
     }
 
+    /**
+     * Request unbind from the service.
+     */
     fun unbindService() {
         lastLoadCallback = null
         onLoadCanceller?.run()
@@ -281,6 +339,9 @@
         }.toString()
     }
 
+    /**
+     * Messages for the internal queue.
+     */
     sealed class Message {
         abstract val type: Int
         object Load : Message() {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ServiceWrapper.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ServiceWrapper.kt
index 5c812b1..b90f892 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ServiceWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ServiceWrapper.kt
@@ -25,12 +25,17 @@
 import android.service.controls.actions.ControlActionWrapper
 import android.util.Log
 
+/**
+ * Wrapper for the service calls.
+ *
+ * Calling all [IControlsProvider] methods through here will wrap them in a try/catch block.
+ */
 class ServiceWrapper(val service: IControlsProvider) {
     companion object {
         private const val TAG = "ServiceWrapper"
     }
 
-    private fun callThroughService(block: () -> Unit): Boolean {
+    private inline fun callThroughService(block: () -> Unit): Boolean {
         try {
             block()
             return true
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
index 1e52371..9952b97 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -21,6 +21,7 @@
 import android.content.Intent
 import android.os.Bundle
 import android.view.LayoutInflater
+import android.view.View
 import android.view.ViewStub
 import android.widget.Button
 import android.widget.TextView
@@ -34,6 +35,7 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.settings.CurrentUserTracker
 import java.util.concurrent.Executor
+import java.util.function.Consumer
 import javax.inject.Inject
 
 class ControlsFavoritingActivity @Inject constructor(
@@ -51,6 +53,7 @@
     private lateinit var adapterAll: ControlAdapter
     private lateinit var recyclerViewFavorites: RecyclerView
     private lateinit var adapterFavorites: ControlAdapter
+    private lateinit var errorText: TextView
     private var component: ComponentName? = null
 
     private var currentModel: FavoriteModel? = null
@@ -96,6 +99,7 @@
 
         val app = intent.getCharSequenceExtra(EXTRA_APP)
         component = intent.getParcelableExtra<ComponentName>(Intent.EXTRA_COMPONENT_NAME)
+        errorText = requireViewById(R.id.error_message)
 
         setUpRecyclerViews()
 
@@ -118,7 +122,10 @@
         }
 
         component?.let {
-            controller.loadForComponent(it) { allControls, favoriteKeys ->
+            controller.loadForComponent(it, Consumer { data ->
+                val allControls = data.allControls
+                val favoriteKeys = data.favoritesIds
+                val error = data.errorOnLoad
                 executor.execute {
                     val favoriteModel = FavoriteModel(
                         allControls,
@@ -128,8 +135,9 @@
                     adapterAll.changeFavoritesModel(favoriteModel)
                     adapterFavorites.changeFavoritesModel(favoriteModel)
                     currentModel = favoriteModel
+                    errorText.visibility = if (error) View.VISIBLE else View.GONE
                 }
-            }
+            })
         }
 
         currentUserTracker.startTracking()
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt
index 34db684..d893caa 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt
@@ -21,11 +21,26 @@
 import com.android.systemui.controls.UserAwareController
 import com.android.systemui.statusbar.policy.CallbackController
 
+/**
+ * Controller for keeping track of services that can be bound given a particular [ServiceListing].
+ */
 interface ControlsListingController :
         CallbackController<ControlsListingController.ControlsListingCallback>,
         UserAwareController {
 
+    /**
+     * @return the current list of services that satisfies the [ServiceListing].
+     */
     fun getCurrentServices(): List<CandidateInfo>
+
+    /**
+     * Get the app label for a given component.
+     *
+     * This call may do Binder calls (to [PackageManager])
+     *
+     * @param name the component name to retrieve the label
+     * @return the label for the component
+     */
     fun getAppLabel(name: ComponentName): CharSequence? = ""
 
     @FunctionalInterface
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 ec3285f..91d2de7 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
@@ -44,12 +44,16 @@
 import android.annotation.IntDef;
 import android.annotation.MainThread;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.app.Notification;
 import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationListenerService.Ranking;
 import android.service.notification.NotificationListenerService.RankingMap;
 import android.service.notification.StatusBarNotification;
 import android.util.ArrayMap;
+import android.util.Pair;
 
 import androidx.annotation.NonNull;
 
@@ -191,59 +195,121 @@
     }
 
     /**
-     * Dismiss a notification on behalf of the user.
+     * Dismisses multiple notifications on behalf of the user.
      */
-    public void dismissNotification(NotificationEntry entry, @NonNull DismissedByUserStats stats) {
+    public void dismissNotifications(
+            List<Pair<NotificationEntry, DismissedByUserStats>> entriesToDismiss) {
         Assert.isMainThread();
-        requireNonNull(stats);
         checkForReentrantCall();
 
-        if (entry != mNotificationSet.get(entry.getKey())) {
-            throw new IllegalStateException("Invalid entry: " + entry.getKey());
-        }
+        final List<NotificationEntry> entriesToLocallyDismiss = new ArrayList<>();
+        for (int i = 0; i < entriesToDismiss.size(); i++) {
+            NotificationEntry entry = entriesToDismiss.get(i).first;
+            DismissedByUserStats stats = entriesToDismiss.get(i).second;
 
-        if (entry.getDismissState() == DISMISSED) {
-            return;
-        }
-
-        updateDismissInterceptors(entry);
-        if (isDismissIntercepted(entry)) {
-            mLogger.logNotifDismissedIntercepted(entry.getKey());
-            return;
-        }
-
-        // Optimistically mark the notification as dismissed -- we'll wait for the signal from
-        // system server before removing it from our notification set.
-        entry.setDismissState(DISMISSED);
-        mLogger.logNotifDismissed(entry.getKey());
-
-        List<NotificationEntry> canceledEntries = new ArrayList<>();
-
-        if (isCanceled(entry)) {
-            canceledEntries.add(entry);
-        } else {
-            // Ask system server to remove it for us
-            try {
-                mStatusBarService.onNotificationClear(
-                        entry.getSbn().getPackageName(),
-                        entry.getSbn().getTag(),
-                        entry.getSbn().getId(),
-                        entry.getSbn().getUser().getIdentifier(),
-                        entry.getSbn().getKey(),
-                        stats.dismissalSurface,
-                        stats.dismissalSentiment,
-                        stats.notificationVisibility);
-            } catch (RemoteException e) {
-                // system process is dead if we're here.
+            requireNonNull(stats);
+            if (entry != mNotificationSet.get(entry.getKey())) {
+                throw new IllegalStateException("Invalid entry: " + entry.getKey());
             }
 
-            // Also mark any children as dismissed as system server will auto-dismiss them as well
-            if (entry.getSbn().getNotification().isGroupSummary()) {
-                for (NotificationEntry otherEntry : mNotificationSet.values()) {
-                    if (shouldAutoDismiss(otherEntry, entry.getSbn().getGroupKey())) {
-                        otherEntry.setDismissState(PARENT_DISMISSED);
-                        if (isCanceled(otherEntry)) {
-                            canceledEntries.add(otherEntry);
+            if (entry.getDismissState() == DISMISSED) {
+                continue;
+            }
+
+            updateDismissInterceptors(entry);
+            if (isDismissIntercepted(entry)) {
+                mLogger.logNotifDismissedIntercepted(entry.getKey());
+                continue;
+            }
+
+            entriesToLocallyDismiss.add(entry);
+            if (!isCanceled(entry)) {
+                // send message to system server if this notification hasn't already been cancelled
+                try {
+                    mStatusBarService.onNotificationClear(
+                            entry.getSbn().getPackageName(),
+                            entry.getSbn().getTag(),
+                            entry.getSbn().getId(),
+                            entry.getSbn().getUser().getIdentifier(),
+                            entry.getSbn().getKey(),
+                            stats.dismissalSurface,
+                            stats.dismissalSentiment,
+                            stats.notificationVisibility);
+                } catch (RemoteException e) {
+                    // system process is dead if we're here.
+                }
+            }
+        }
+
+        locallyDismissNotifications(entriesToLocallyDismiss);
+        rebuildList();
+    }
+
+    /**
+     * Dismisses a single notification on behalf of the user.
+     */
+    public void dismissNotification(
+            NotificationEntry entry,
+            @NonNull DismissedByUserStats stats) {
+        dismissNotifications(List.of(
+                new Pair<NotificationEntry, DismissedByUserStats>(entry, stats)));
+    }
+
+    /**
+     * Dismisses all clearable notifications for a given userid on behalf of the user.
+     */
+    public void dismissAllNotifications(@UserIdInt int userId) {
+        Assert.isMainThread();
+        checkForReentrantCall();
+
+        try {
+            mStatusBarService.onClearAllNotifications(userId);
+        } catch (RemoteException e) {
+            // system process is dead if we're here.
+        }
+
+        final List<NotificationEntry> entries = new ArrayList(getActiveNotifs());
+        for (int i = entries.size() - 1; i >= 0; i--) {
+            NotificationEntry entry = entries.get(i);
+            if (!shouldDismissOnClearAll(entry, userId)) {
+                // system server won't be removing these notifications, but we still give dismiss
+                // interceptors the chance to filter the notification
+                updateDismissInterceptors(entry);
+                if (isDismissIntercepted(entry)) {
+                    mLogger.logNotifClearAllDismissalIntercepted(entry.getKey());
+                }
+                entries.remove(i);
+            }
+        }
+
+        locallyDismissNotifications(entries);
+        rebuildList();
+    }
+
+    /**
+     * Optimistically marks the given notifications as dismissed -- we'll wait for the signal
+     * from system server before removing it from our notification set.
+     */
+    private void locallyDismissNotifications(List<NotificationEntry> entries) {
+        final List<NotificationEntry> canceledEntries = new ArrayList<>();
+
+        for (int i = 0; i < entries.size(); i++) {
+            NotificationEntry entry = entries.get(i);
+
+            entry.setDismissState(DISMISSED);
+            mLogger.logNotifDismissed(entry.getKey());
+
+            if (isCanceled(entry)) {
+                canceledEntries.add(entry);
+            } else {
+                // Mark any children as dismissed as system server will auto-dismiss them as well
+                if (entry.getSbn().getNotification().isGroupSummary()) {
+                    for (NotificationEntry otherEntry : mNotificationSet.values()) {
+                        if (shouldAutoDismissChildren(otherEntry, entry.getSbn().getGroupKey())) {
+                            otherEntry.setDismissState(PARENT_DISMISSED);
+                            if (isCanceled(otherEntry)) {
+                                canceledEntries.add(otherEntry);
+                            }
                         }
                     }
                 }
@@ -255,7 +321,6 @@
         for (NotificationEntry canceledEntry : canceledEntries) {
             tryRemoveNotification(canceledEntry);
         }
-        rebuildList();
     }
 
     private void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
@@ -552,7 +617,7 @@
      *
      * See NotificationManager.cancelGroupChildrenByListLocked() for corresponding code.
      */
-    private static boolean shouldAutoDismiss(
+    private static boolean shouldAutoDismissChildren(
             NotificationEntry entry,
             String dismissedGroupKey) {
         return entry.getSbn().getGroupKey().equals(dismissedGroupKey)
@@ -562,10 +627,39 @@
                 && entry.getDismissState() != DISMISSED;
     }
 
+    /**
+     * When the user 'clears all notifications' through SystemUI, NotificationManager will not
+     * dismiss unclearable notifications.
+     * @return true if we think NotificationManager will dismiss the entry when asked to
+     * cancel this notification with {@link NotificationListenerService#REASON_CANCEL_ALL}
+     *
+     * See NotificationManager.cancelAllLocked for corresponding code.
+     */
+    private static boolean shouldDismissOnClearAll(
+            NotificationEntry entry,
+            @UserIdInt int userId) {
+        return userIdMatches(entry, userId)
+                && entry.isClearable()
+                && !hasFlag(entry, Notification.FLAG_BUBBLE)
+                && entry.getDismissState() != DISMISSED;
+    }
+
     private static boolean hasFlag(NotificationEntry entry, int flag) {
         return (entry.getSbn().getNotification().flags & flag) != 0;
     }
 
+    /**
+     * Determine whether the userId applies to the notification in question, either because
+     * they match exactly, or one of them is USER_ALL (which is treated as a wildcard).
+     *
+     * See NotificationManager#notificationMatchesUserId
+     */
+    private static boolean userIdMatches(NotificationEntry entry, int userId) {
+        return userId == UserHandle.USER_ALL
+                || entry.getSbn().getUser().getIdentifier() == UserHandle.USER_ALL
+                || entry.getSbn().getUser().getIdentifier() == userId;
+    }
+
     private void dispatchOnEntryInit(NotificationEntry entry) {
         mAmDispatchingToOtherCode = true;
         for (NotifCollectionListener listener : mNotifCollectionListeners) {
@@ -613,6 +707,7 @@
         }
         mAmDispatchingToOtherCode = false;
     }
+
     @Override
     public void dump(@NonNull FileDescriptor fd, PrintWriter pw, @NonNull String[] args) {
         final List<NotificationEntry> entries = new ArrayList<>(getActiveNotifs());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
index 83f56cc..1f6413b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
@@ -44,6 +44,7 @@
     private final IStatusBarService mStatusBarService;
     private final NotifCollection mNotifCollection;
     private final NotifInflationErrorManager mNotifErrorManager;
+    private final NotifPipeline mNotifPipeline;
 
     private NotificationRowBinderImpl mNotificationRowBinder;
     private InflationCallback mExternalInflationCallback;
@@ -52,10 +53,12 @@
     public NotifInflaterImpl(
             IStatusBarService statusBarService,
             NotifCollection notifCollection,
-            NotifInflationErrorManager errorManager) {
+            NotifInflationErrorManager errorManager,
+            NotifPipeline notifPipeline) {
         mStatusBarService = statusBarService;
         mNotifCollection = notifCollection;
         mNotifErrorManager = errorManager;
+        mNotifPipeline = notifPipeline;
     }
 
     /**
@@ -110,7 +113,7 @@
                                 DISMISS_SENTIMENT_NEUTRAL,
                                 NotificationVisibility.obtain(entry.getKey(),
                                         entry.getRanking().getRank(),
-                                        mNotifCollection.getActiveNotifs().size(),
+                                        mNotifPipeline.getShadeListCount(),
                                         true,
                                         NotificationLogger.getNotificationLocation(entry))
                         ));
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 d4d2369..44cec96 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
@@ -195,4 +195,27 @@
     public List<ListEntry> getShadeList() {
         return mShadeListBuilder.getShadeList();
     }
+
+    /**
+     * Returns the number of notifications currently shown in the shade. This includes all
+     * children and summary notifications. If this method is called during pipeline execution it
+     * will return the number of notifications in its current state, which will likely be only
+     * partially-generated.
+     */
+    public int getShadeListCount() {
+        final List<ListEntry> entries = getShadeList();
+        int numNotifs = 0;
+        for (int i = 0; i < entries.size(); i++) {
+            final ListEntry entry = entries.get(i);
+            if (entry instanceof GroupEntry) {
+                final GroupEntry parentEntry = (GroupEntry) entry;
+                numNotifs++; // include the summary in the count
+                numNotifs += parentEntry.getChildren().size();
+            } else {
+                numNotifs++;
+            }
+        }
+
+        return numNotifs;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
index 116c70c..8b2a07d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
@@ -17,7 +17,7 @@
 package com.android.systemui.statusbar.notification.collection.coordinator;
 
 import static android.service.notification.NotificationStats.DISMISSAL_OTHER;
-import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_UNKNOWN;
+import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
 
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.bubbles.BubbleController;
@@ -153,10 +153,10 @@
     private DismissedByUserStats createDismissedByUserStats(NotificationEntry entry) {
         return new DismissedByUserStats(
                 DISMISSAL_OTHER,
-                DISMISS_SENTIMENT_UNKNOWN,
+                DISMISS_SENTIMENT_NEUTRAL,
                 NotificationVisibility.obtain(entry.getKey(),
                         entry.getRanking().getRank(),
-                        mNotifPipeline.getActiveNotifs().size(),
+                        mNotifPipeline.getShadeListCount(),
                         true, // was visible as a bubble
                         NotificationLogger.getNotificationLocation(entry))
         );
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
index dc7a50d..8675cca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
@@ -77,6 +77,14 @@
         })
     }
 
+    fun logNotifClearAllDismissalIntercepted(key: String) {
+        buffer.log(TAG, INFO, {
+            str1 = key
+        }, {
+            "CLEAR ALL DISMISSAL INTERCEPTED $str1"
+        })
+    }
+
     fun logRankingMissing(key: String, rankingMap: RankingMap) {
         buffer.log(TAG, WARNING, { str1 = key }, { "Ranking update is missing ranking for $str1" })
         buffer.log(TAG, DEBUG, {}, { "Ranking map contents:" })
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 0cc3371..b2b46d5 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
@@ -16,6 +16,9 @@
 
 package com.android.systemui.statusbar.notification.stack;
 
+import static android.service.notification.NotificationStats.DISMISSAL_SHADE;
+import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
+
 import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
 import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
 import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;
@@ -79,6 +82,7 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.statusbar.NotificationVisibility;
 import com.android.keyguard.KeyguardSliceView;
 import com.android.settingslib.Utils;
 import com.android.systemui.Dependency;
@@ -98,6 +102,7 @@
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.DragDownHelper.DragDownCallback;
 import com.android.systemui.statusbar.EmptyShadeView;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -114,7 +119,11 @@
 import com.android.systemui.statusbar.notification.ShadeViewRefactor;
 import com.android.systemui.statusbar.notification.ShadeViewRefactor.RefactorComponent;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -484,8 +493,10 @@
     private NotificationIconAreaController mIconAreaController;
     private final NotificationLockscreenUserManager mLockscreenUserManager;
     private final Rect mTmpRect = new Rect();
-    private final NotificationEntryManager mEntryManager =
-            Dependency.get(NotificationEntryManager.class);
+    private final FeatureFlags mFeatureFlags;
+    private final NotifPipeline mNotifPipeline;
+    private final NotifCollection mNotifCollection;
+    private final NotificationEntryManager mEntryManager;
     private final IStatusBarService mBarService = IStatusBarService.Stub.asInterface(
             ServiceManager.getService(Context.STATUS_BAR_SERVICE));
     @VisibleForTesting
@@ -529,7 +540,11 @@
             ZenModeController zenController,
             NotificationSectionsManager notificationSectionsManager,
             ForegroundServiceSectionController fgsSectionController,
-            ForegroundServiceDismissalFeatureController fgsFeatureController
+            ForegroundServiceDismissalFeatureController fgsFeatureController,
+            FeatureFlags featureFlags,
+            NotifPipeline notifPipeline,
+            NotificationEntryManager entryManager,
+            NotifCollection notifCollection
     ) {
         super(context, attrs, 0, 0);
         Resources res = getResources();
@@ -607,16 +622,26 @@
             }
         }, HIGH_PRIORITY, Settings.Secure.NOTIFICATION_DISMISS_RTL);
 
-        mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
-            @Override
-            public void onPreEntryUpdated(NotificationEntry entry) {
-                if (entry.rowExists() && !entry.getSbn().isClearable()) {
-                    // If the row already exists, the user may have performed a dismiss action on
-                    // the notification. Since it's not clearable we should snap it back.
-                    snapViewIfNeeded(entry);
+        mFeatureFlags = featureFlags;
+        mNotifPipeline = notifPipeline;
+        mEntryManager = entryManager;
+        mNotifCollection = notifCollection;
+        if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+            mNotifPipeline.addCollectionListener(new NotifCollectionListener() {
+                @Override
+                public void onEntryUpdated(NotificationEntry entry) {
+                    NotificationStackScrollLayout.this.onEntryUpdated(entry);
                 }
-            }
-        });
+            });
+        } else {
+            mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
+                @Override
+                public void onPreEntryUpdated(NotificationEntry entry) {
+                    NotificationStackScrollLayout.this.onEntryUpdated(entry);
+                }
+            });
+        }
+
         dynamicPrivacyController.addListener(this);
         mDynamicPrivacyController = dynamicPrivacyController;
         mStatusbarStateController = statusBarStateController;
@@ -708,7 +733,7 @@
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void updateFooter() {
         boolean showDismissView = mClearAllEnabled && hasActiveClearableNotifications(ROWS_ALL);
-        boolean showFooterView = (showDismissView || mEntryManager.hasActiveNotifications())
+        boolean showFooterView = (showDismissView || hasActiveNotifications())
                 && mStatusBarState != StatusBarState.KEYGUARD
                 && !mRemoteInputManager.getController().isRemoteInputActive();
 
@@ -5537,32 +5562,10 @@
             return;
         }
 
-        performDismissAllAnimations(viewsToHide, closeShade, () -> {
-            for (ExpandableNotificationRow rowToRemove : viewsToRemove) {
-                if (canChildBeDismissed(rowToRemove)) {
-                    if (selection == ROWS_ALL) {
-                        // TODO: This is a listener method; we shouldn't be calling it. Can we just
-                        // call performRemoveNotification as below?
-                        mEntryManager.removeNotification(
-                                rowToRemove.getEntry().getKey(),
-                                null /* ranking */,
-                                NotificationListenerService.REASON_CANCEL_ALL);
-                    } else {
-                        mEntryManager.performRemoveNotification(
-                                rowToRemove.getEntry().getSbn(),
-                                NotificationListenerService.REASON_CANCEL_ALL);
-                    }
-                } else {
-                    rowToRemove.resetTranslation();
-                }
-            }
-            if (selection == ROWS_ALL) {
-                try {
-                    mBarService.onClearAllNotifications(mLockscreenUserManager.getCurrentUserId());
-                } catch (Exception ex) {
-                }
-            }
-        });
+        performDismissAllAnimations(
+                viewsToHide,
+                closeShade,
+                () -> onDismissAllAnimationsEnd(viewsToRemove, selection));
     }
 
     private boolean includeChildInDismissAll(
@@ -6407,6 +6410,83 @@
         return false;
     }
 
+    // --------------------- NotificationEntryManager/NotifPipeline methods ------------------------
+
+    private void onEntryUpdated(NotificationEntry entry) {
+        // If the row already exists, the user may have performed a dismiss action on the
+        // notification. Since it's not clearable we should snap it back.
+        if (entry.rowExists() && !entry.getSbn().isClearable()) {
+            snapViewIfNeeded(entry);
+        }
+    }
+
+    private boolean hasActiveNotifications() {
+        if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+            return mNotifPipeline.getShadeList().isEmpty();
+        } else {
+            return mEntryManager.hasActiveNotifications();
+        }
+    }
+
+    /**
+     * Called after the animations for a "clear all notifications" action has ended.
+     */
+    private void onDismissAllAnimationsEnd(
+            List<ExpandableNotificationRow> viewsToRemove,
+            @SelectedRows int selectedRows) {
+        if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+            if (selectedRows == ROWS_ALL) {
+                mNotifCollection.dismissAllNotifications(mLockscreenUserManager.getCurrentUserId());
+            } else {
+                final List<Pair<NotificationEntry, DismissedByUserStats>>
+                        entriesWithRowsDismissedFromShade = new ArrayList<>();
+                final List<DismissedByUserStats> dismissalUserStats = new ArrayList<>();
+                final int numVisibleEntries = mNotifPipeline.getShadeListCount();
+                for (int i = 0; i < viewsToRemove.size(); i++) {
+                    final NotificationEntry entry = viewsToRemove.get(i).getEntry();
+                    final DismissedByUserStats stats =
+                            new DismissedByUserStats(
+                                    DISMISSAL_SHADE,
+                                    DISMISS_SENTIMENT_NEUTRAL,
+                                    NotificationVisibility.obtain(
+                                            entry.getKey(),
+                                            entry.getRanking().getRank(),
+                                            numVisibleEntries,
+                                            true,
+                                            NotificationLogger.getNotificationLocation(entry)));
+                    entriesWithRowsDismissedFromShade.add(
+                            new Pair<NotificationEntry, DismissedByUserStats>(entry, stats));
+                }
+                mNotifCollection.dismissNotifications(entriesWithRowsDismissedFromShade);
+            }
+        } else {
+            for (ExpandableNotificationRow rowToRemove : viewsToRemove) {
+                if (canChildBeDismissed(rowToRemove)) {
+                    if (selectedRows == ROWS_ALL) {
+                        // TODO: This is a listener method; we shouldn't be calling it. Can we just
+                        // call performRemoveNotification as below?
+                        mEntryManager.removeNotification(
+                                rowToRemove.getEntry().getKey(),
+                                null /* ranking */,
+                                NotificationListenerService.REASON_CANCEL_ALL);
+                    } else {
+                        mEntryManager.performRemoveNotification(
+                                rowToRemove.getEntry().getSbn(),
+                                NotificationListenerService.REASON_CANCEL_ALL);
+                    }
+                } else {
+                    rowToRemove.resetTranslation();
+                }
+            }
+            if (selectedRows == ROWS_ALL) {
+                try {
+                    mBarService.onClearAllNotifications(mLockscreenUserManager.getCurrentUserId());
+                } catch (Exception ex) {
+                }
+            }
+        }
+    }
+
     // ---------------------- DragDownHelper.OnDragDownListener ------------------------------------
 
     @ShadeViewRefactor(RefactorComponent.INPUT)
@@ -6415,8 +6495,7 @@
         /* Only ever called as a consequence of a lockscreen expansion gesture. */
         @Override
         public boolean onDraggedDown(View startingChild, int dragLengthY) {
-            if (mStatusBarState == StatusBarState.KEYGUARD
-                    && mEntryManager.hasActiveNotifications()) {
+            if (mStatusBarState == StatusBarState.KEYGUARD && hasActiveNotifications()) {
                 mLockscreenGestureLogger.write(
                         MetricsEvent.ACTION_LS_SHADE,
                         (int) (dragLengthY / mDisplayMetrics.density),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
index 40b1610..d016217 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
@@ -48,7 +48,6 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.statusbar.RemoteInputController.Callback;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.SuperStatusBarViewFactory;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -96,15 +95,13 @@
             mCallbacks = Lists.newArrayList();
 
     private final SysuiColorExtractor mColorExtractor;
-    private final SuperStatusBarViewFactory mSuperStatusBarViewFactory;
 
     @Inject
     public NotificationShadeWindowController(Context context, WindowManager windowManager,
             IActivityManager activityManager, DozeParameters dozeParameters,
             StatusBarStateController statusBarStateController,
             ConfigurationController configurationController,
-            KeyguardBypassController keyguardBypassController, SysuiColorExtractor colorExtractor,
-            SuperStatusBarViewFactory superStatusBarViewFactory) {
+            KeyguardBypassController keyguardBypassController, SysuiColorExtractor colorExtractor) {
         mContext = context;
         mWindowManager = windowManager;
         mActivityManager = activityManager;
@@ -114,8 +111,6 @@
         mLpChanged = new LayoutParams();
         mKeyguardBypassController = keyguardBypassController;
         mColorExtractor = colorExtractor;
-        mSuperStatusBarViewFactory = superStatusBarViewFactory;
-        mNotificationShadeView = mSuperStatusBarViewFactory.getNotificationShadeWindowView();
 
         mLockScreenDisplayTimeout = context.getResources()
                 .getInteger(R.integer.config_lockScreenDisplayTimeout);
@@ -194,6 +189,10 @@
         onThemeChanged();
     }
 
+    public void setNotificationShadeView(ViewGroup view) {
+        mNotificationShadeView = view;
+    }
+
     public ViewGroup getNotificationShadeView() {
         return mNotificationShadeView;
     }
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 4f01cc1..823adff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -1353,6 +1353,7 @@
                 .statusBarWindowView(mNotificationShadeWindowView).build();
         mNotificationShadeWindowViewController = statusBarComponent
                 .getNotificationShadeWindowViewController();
+        mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView);
         mNotificationShadeWindowViewController.setupExpandedStatusBar();
         mStatusBarWindowController = statusBarComponent.getStatusBarWindowController();
         mPhoneStatusBarWindow = mSuperStatusBarViewFactory.getStatusBarWindowView();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 0f3b5db..e1a20b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.phone;
 
 import static android.service.notification.NotificationListenerService.REASON_CLICK;
+import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
 
 import static com.android.systemui.statusbar.phone.StatusBar.getActivityOptions;
 
@@ -35,6 +36,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.service.dreams.IDreamManager;
+import android.service.notification.NotificationStats;
 import android.service.notification.StatusBarNotification;
 import android.text.TextUtils;
 import android.util.EventLog;
@@ -56,6 +58,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -66,7 +69,11 @@
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.policy.HeadsUpUtil;
@@ -97,6 +104,9 @@
     private final KeyguardStateController mKeyguardStateController;
     private final ActivityStarter mActivityStarter;
     private final NotificationEntryManager mEntryManager;
+    private final NotifPipeline mNotifPipeline;
+    private final NotifCollection mNotifCollection;
+    private final FeatureFlags mFeatureFlags;
     private final StatusBarStateController mStatusBarStateController;
     private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
     private final MetricsLogger mMetricsLogger;
@@ -135,7 +145,9 @@
             NotificationInterruptionStateProvider notificationInterruptionStateProvider,
             MetricsLogger metricsLogger, LockPatternUtils lockPatternUtils,
             Handler mainThreadHandler, Handler backgroundHandler, Executor uiBgExecutor,
-            ActivityIntentHelper activityIntentHelper, BubbleController bubbleController) {
+            ActivityIntentHelper activityIntentHelper, BubbleController bubbleController,
+            FeatureFlags featureFlags, NotifPipeline notifPipeline,
+            NotifCollection notifCollection) {
         mContext = context;
         mNotificationPanel = panel;
         mPresenter = presenter;
@@ -162,12 +174,25 @@
         mLockPatternUtils = lockPatternUtils;
         mBackgroundHandler = backgroundHandler;
         mUiBgExecutor = uiBgExecutor;
-        mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
-            @Override
-            public void onPendingEntryAdded(NotificationEntry entry) {
-                handleFullScreenIntent(entry);
-            }
-        });
+        mFeatureFlags = featureFlags;
+        mNotifPipeline = notifPipeline;
+        mNotifCollection = notifCollection;
+        if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+            mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
+                @Override
+                public void onPendingEntryAdded(NotificationEntry entry) {
+                    handleFullScreenIntent(entry);
+                }
+            });
+        } else {
+            mNotifPipeline.addCollectionListener(new NotifCollectionListener() {
+                @Override
+                public void onEntryAdded(NotificationEntry entry) {
+                    handleFullScreenIntent(entry);
+                }
+            });
+        }
+
         mStatusBarRemoteInputCallback = remoteInputCallback;
         mMainThreadHandler = mainThreadHandler;
         mActivityIntentHelper = activityIntentHelper;
@@ -246,15 +271,14 @@
             mHeadsUpManager.removeNotification(sbn.getKey(),
                     true /* releaseImmediately */);
         }
-        StatusBarNotification parentToCancel = null;
+        NotificationEntry parentToCancel = null;
         if (shouldAutoCancel(sbn) && mGroupManager.isOnlyChildInGroup(sbn)) {
-            StatusBarNotification summarySbn =
-                    mGroupManager.getLogicalGroupSummary(sbn).getSbn();
-            if (shouldAutoCancel(summarySbn)) {
+            NotificationEntry summarySbn = mGroupManager.getLogicalGroupSummary(sbn);
+            if (shouldAutoCancel(summarySbn.getSbn())) {
                 parentToCancel = summarySbn;
             }
         }
-        final StatusBarNotification parentToCancelFinal = parentToCancel;
+        final NotificationEntry parentToCancelFinal = parentToCancel;
         final Runnable runnable = () -> handleNotificationClickAfterPanelCollapsed(
                 sbn, row, controller, intent,
                 isActivityIntent, wasOccluded, parentToCancelFinal);
@@ -279,7 +303,7 @@
             PendingIntent intent,
             boolean isActivityIntent,
             boolean wasOccluded,
-            StatusBarNotification parentToCancelFinal) {
+            NotificationEntry parentToCancelFinal) {
         String notificationKey = sbn.getKey();
         try {
             // The intent we are sending is for the application, which
@@ -330,7 +354,7 @@
             collapseOnMainThread();
         }
 
-        final int count = mEntryManager.getActiveNotificationsCount();
+        final int count = getVisibleNotificationsCount();
         final int rank = entry.getRanking().getRank();
         NotificationVisibility.NotificationLocation location =
                 NotificationLogger.getNotificationLocation(entry);
@@ -341,15 +365,19 @@
         } catch (RemoteException ex) {
             // system process is dead if we're here.
         }
+
         if (!isBubble) {
             if (parentToCancelFinal != null) {
+                // TODO: (b/145659174) remove - this cancels the parent if the notification clicked
+                // on will auto-cancel and is the only child in the group. This won't be
+                // necessary in the new pipeline due to group pruning in ShadeListBuilder.
                 removeNotification(parentToCancelFinal);
             }
             if (shouldAutoCancel(sbn)
                     || mRemoteInputManager.isNotificationKeptForRemoteInputHistory(
                     notificationKey)) {
                 // Automatically remove all notifications that we may have kept around longer
-                removeNotification(sbn);
+                removeNotification(row.getEntry());
             }
         }
         mIsCollapsingToShowActivityOverLockscreen = false;
@@ -482,11 +510,10 @@
         return entry.shouldSuppressFullScreenIntent();
     }
 
-    private void removeNotification(StatusBarNotification notification) {
+    private void removeNotification(NotificationEntry entry) {
         // We have to post it to the UI thread for synchronization
         mMainThreadHandler.post(() -> {
-            Runnable removeRunnable =
-                    () -> mEntryManager.performRemoveNotification(notification, REASON_CLICK);
+            Runnable removeRunnable = createRemoveRunnable(entry);
             if (mPresenter.isCollapsing()) {
                 // To avoid lags we're only performing the remove
                 // after the shade was collapsed
@@ -497,6 +524,53 @@
         });
     }
 
+    // --------------------- NotificationEntryManager/NotifPipeline methods ------------------------
+
+    private int getVisibleNotificationsCount() {
+        if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+            return mNotifPipeline.getShadeListCount();
+        } else {
+            return mEntryManager.getActiveNotificationsCount();
+        }
+    }
+
+    private Runnable createRemoveRunnable(NotificationEntry entry) {
+        if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+            return new Runnable() {
+                @Override
+                public void run() {
+                    // see NotificationLogger#logNotificationClear
+                    int dismissalSurface = NotificationStats.DISMISSAL_SHADE;
+                    if (mHeadsUpManager.isAlerting(entry.getKey())) {
+                        dismissalSurface = NotificationStats.DISMISSAL_PEEK;
+                    } else if (mNotificationPanel.hasPulsingNotifications()) {
+                        dismissalSurface = NotificationStats.DISMISSAL_AOD;
+                    }
+
+                    mNotifCollection.dismissNotification(
+                            entry,
+                            new DismissedByUserStats(
+                                    dismissalSurface,
+                                    DISMISS_SENTIMENT_NEUTRAL,
+                                    NotificationVisibility.obtain(
+                                            entry.getKey(),
+                                            entry.getRanking().getRank(),
+                                            mNotifPipeline.getShadeListCount(),
+                                            true,
+                                            NotificationLogger.getNotificationLocation(entry))
+                            ));
+                }
+            };
+        } else {
+            return new Runnable() {
+                @Override
+                public void run() {
+                    mEntryManager.performRemoveNotification(entry.getSbn(), REASON_CLICK);
+                }
+            };
+        }
+    }
+
     /**
      * Public builder for {@link StatusBarNotificationActivityStarter}.
      */
@@ -506,6 +580,9 @@
         private final CommandQueue mCommandQueue;
         private final Lazy<AssistManager> mAssistManagerLazy;
         private final NotificationEntryManager mEntryManager;
+        private final FeatureFlags mFeatureFlags;
+        private final NotifPipeline mNotifPipeline;
+        private final NotifCollection mNotifCollection;
         private final HeadsUpManagerPhone mHeadsUpManager;
         private final ActivityStarter mActivityStarter;
         private final IStatusBarService mStatusBarService;
@@ -557,7 +634,10 @@
                 @UiBackground Executor uiBgExecutor,
                 ActivityIntentHelper activityIntentHelper,
                 BubbleController bubbleController,
-                ShadeController shadeController) {
+                ShadeController shadeController,
+                FeatureFlags featureFlags,
+                NotifPipeline notifPipeline,
+                NotifCollection notifCollection) {
             mContext = context;
             mCommandQueue = commandQueue;
             mAssistManagerLazy = assistManagerLazy;
@@ -583,6 +663,9 @@
             mActivityIntentHelper = activityIntentHelper;
             mBubbleController = bubbleController;
             mShadeController = shadeController;
+            mFeatureFlags = featureFlags;
+            mNotifPipeline = notifPipeline;
+            mNotifCollection = notifCollection;
         }
 
         /** Sets the status bar to use as {@link StatusBar}. */
@@ -608,8 +691,6 @@
             return this;
         }
 
-
-
         public StatusBarNotificationActivityStarter build() {
             return new StatusBarNotificationActivityStarter(mContext,
                     mCommandQueue, mAssistManagerLazy,
@@ -638,7 +719,10 @@
                     mBackgroundHandler,
                     mUiBgExecutor,
                     mActivityIntentHelper,
-                    mBubbleController);
+                    mBubbleController,
+                    mFeatureFlags,
+                    mNotifPipeline,
+                    mNotifCollection);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
index dea8c5d..edab4a7 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
@@ -38,6 +38,7 @@
 import android.widget.Toast;
 
 import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.SystemUI;
 import com.android.systemui.statusbar.CommandQueue;
 
@@ -67,12 +68,21 @@
 
     @Inject
     public ToastUI(Context context, CommandQueue commandQueue) {
+        this(context, commandQueue,
+                (WindowManager) context.getSystemService(Context.WINDOW_SERVICE),
+                INotificationManager.Stub.asInterface(
+                        ServiceManager.getService(Context.NOTIFICATION_SERVICE)),
+                AccessibilityManager.getInstance(context));
+    }
+
+    @VisibleForTesting
+    ToastUI(Context context, CommandQueue commandQueue, WindowManager windowManager,
+            INotificationManager notificationManager, AccessibilityManager accessibilityManager) {
         super(context);
         mCommandQueue = commandQueue;
-        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
-        mNotificationManager = INotificationManager.Stub.asInterface(
-                ServiceManager.getService(Context.NOTIFICATION_SERVICE));
-        mAccessibilityManager = AccessibilityManager.getInstance(context);
+        mWindowManager = windowManager;
+        mNotificationManager = notificationManager;
+        mAccessibilityManager = accessibilityManager;
     }
 
     @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MirrorWindowControlTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MirrorWindowControlTest.java
new file mode 100644
index 0000000..fff7e4a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MirrorWindowControlTest.java
@@ -0,0 +1,148 @@
+/*
+ * 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.accessibility;
+
+import static android.view.WindowManager.LayoutParams;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.graphics.Point;
+import android.os.IBinder;
+import android.testing.AndroidTestingRunner;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class MirrorWindowControlTest extends SysuiTestCase {
+
+    @Mock WindowManager mWindowManager;
+    @Mock IBinder mIBinder;
+    View mView;
+    int mViewWidth;
+    int mViewHeight;
+
+    StubMirrorWindowControl mStubMirrorWindowControl;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mView = new View(getContext());
+        mViewWidth = 10;
+        mViewHeight = 20;
+        getContext().addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
+        doAnswer(invocation -> {
+            View view = invocation.getArgument(0);
+            LayoutParams lp = invocation.getArgument(1);
+            view.setLayoutParams(lp);
+            return null;
+        }).when(mWindowManager).addView(any(View.class), any(LayoutParams.class));
+
+        mStubMirrorWindowControl = new StubMirrorWindowControl(getContext(), mView, mViewWidth,
+                mViewHeight);
+    }
+
+    @Test
+    public void showControl_createViewAndAddView() {
+        mStubMirrorWindowControl.showControl(mIBinder);
+
+        assertTrue(mStubMirrorWindowControl.mInvokeOnCreateView);
+        ArgumentCaptor<ViewGroup.LayoutParams> lpCaptor = ArgumentCaptor.forClass(
+                ViewGroup.LayoutParams.class);
+        verify(mWindowManager).addView(any(), lpCaptor.capture());
+        assertTrue(lpCaptor.getValue().width == mViewWidth);
+        assertTrue(lpCaptor.getValue().height == mViewHeight);
+    }
+
+    @Test
+    public void destroyControl_removeView() {
+        mStubMirrorWindowControl.showControl(mIBinder);
+        ArgumentCaptor<View> captor = ArgumentCaptor.forClass(View.class);
+        verify(mWindowManager).addView(captor.capture(), any(LayoutParams.class));
+
+        mStubMirrorWindowControl.destroyControl();
+
+        verify(mWindowManager).removeView(eq(captor.getValue()));
+    }
+
+    @Test
+    public void move_offsetIsCorrect() {
+        ArgumentCaptor<ViewGroup.LayoutParams> lpCaptor = ArgumentCaptor.forClass(
+                ViewGroup.LayoutParams.class);
+        mStubMirrorWindowControl.showControl(mIBinder);
+        verify(mWindowManager).addView(any(), lpCaptor.capture());
+        LayoutParams lp = (LayoutParams) lpCaptor.getValue();
+        Point startPosition = new Point(lp.x, lp.y);
+
+        mStubMirrorWindowControl.move(-10, -20);
+
+        verify(mWindowManager).updateViewLayout(eq(mView), lpCaptor.capture());
+        assertTrue(lpCaptor.getAllValues().size() == 2);
+        lp = (LayoutParams) lpCaptor.getValue();
+        Point currentPosition = new Point(lp.x, lp.y);
+        assertEquals(-10, currentPosition.x - startPosition.x);
+        assertEquals(-20, currentPosition.y - startPosition.y);
+    }
+
+    private static class StubMirrorWindowControl extends MirrorWindowControl {
+        private final int mWidth;
+        private final int mHeight;
+        private final View mView;
+
+        boolean mInvokeOnCreateView = false;
+
+        StubMirrorWindowControl(Context context, View view, int width, int height) {
+            super(context);
+            mView = view;
+            mWidth = width;
+            mHeight = height;
+        }
+
+        @Override
+        public String getWindowTitle() {
+            return "StubMirrorWindowControl";
+        }
+
+        @Override
+        View onCreateView(LayoutInflater inflater, Point viewSize) {
+            mInvokeOnCreateView = true;
+            viewSize.x = mWidth;
+            viewSize.y = mHeight;
+            return mView;
+        }
+
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
new file mode 100644
index 0000000..08a6172
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.accessibility;
+
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+
+import android.app.Instrumentation;
+import android.os.IBinder;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class WindowMagnificationControllerTest extends SysuiTestCase {
+
+    @Mock
+    MirrorWindowControl mMirrorWindowControl;
+    private WindowMagnificationController mWindowMagnificationController;
+    private Instrumentation mInstrumentation;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mWindowMagnificationController = new WindowMagnificationController(getContext(),
+                mMirrorWindowControl);
+        verify(mMirrorWindowControl).setWindowDelegate(
+                any(MirrorWindowControl.MirrorWindowDelegate.class));
+    }
+
+    @After
+    public void tearDown() {
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.deleteWindowMagnification();
+        });
+        mInstrumentation.waitForIdleSync();
+    }
+
+    @Test
+    public void createWindowMagnification_showControl() {
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.createWindowMagnification();
+        });
+        mInstrumentation.waitForIdleSync();
+        verify(mMirrorWindowControl).showControl(any(IBinder.class));
+    }
+
+    @Test
+    public void deleteWindowMagnification_destroyControl() {
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.createWindowMagnification();
+        });
+        mInstrumentation.waitForIdleSync();
+
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.deleteWindowMagnification();
+        });
+        mInstrumentation.waitForIdleSync();
+
+        verify(mMirrorWindowControl).destroyControl();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index c3b55e2..7d47f6b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -201,8 +201,9 @@
         // Bubbles get added to status bar window view
         mNotificationShadeWindowController = new NotificationShadeWindowController(mContext,
                 mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
-                mConfigurationController, mKeyguardBypassController, mColorExtractor,
-                mSuperStatusBarViewFactory);
+                mConfigurationController, mKeyguardBypassController, mColorExtractor);
+        mNotificationShadeWindowController.setNotificationShadeView(
+                mSuperStatusBarViewFactory.getNotificationShadeWindowView());
         mNotificationShadeWindowController.attach();
 
         // Need notifications for bubbles
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
index 72405fc..5a1bef9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
@@ -195,8 +195,9 @@
         // Bubbles get added to status bar window view
         mNotificationShadeWindowController = new NotificationShadeWindowController(mContext,
                 mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
-                mConfigurationController, mKeyguardBypassController, mColorExtractor,
-                mSuperStatusBarViewFactory);
+                mConfigurationController, mKeyguardBypassController, mColorExtractor);
+        mNotificationShadeWindowController.setNotificationShadeView(
+                mSuperStatusBarViewFactory.getNotificationShadeWindowView());
         mNotificationShadeWindowController.attach();
 
         // Need notifications for bubbles
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
index 89c1636..40075c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
@@ -86,7 +86,11 @@
 
     @Test
     fun testBindAndLoad() {
-        val callback: (List<Control>) -> Unit = {}
+        val callback = object : ControlsBindingController.LoadCallback {
+            override fun error(message: String) {}
+
+            override fun accept(t: List<Control>) {}
+        }
         controller.bindAndLoad(TEST_COMPONENT_NAME_1, callback)
 
         assertEquals(1, providers.size)
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
index 751217f..e5ec2dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
@@ -56,6 +56,7 @@
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 import java.util.Optional
+import java.util.function.Consumer
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -79,7 +80,8 @@
     @Captor
     private lateinit var controlInfoListCaptor: ArgumentCaptor<List<ControlInfo>>
     @Captor
-    private lateinit var controlLoadCallbackCaptor: ArgumentCaptor<(List<Control>) -> Unit>
+    private lateinit var controlLoadCallbackCaptor:
+            ArgumentCaptor<ControlsBindingController.LoadCallback>
     @Captor
     private lateinit var broadcastReceiverCaptor: ArgumentCaptor<BroadcastReceiver>
 
@@ -226,13 +228,13 @@
         val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2)
         val control = builderFromInfo(newControlInfo).build()
 
-        controller.loadForComponent(TEST_COMPONENT) { _, _ -> Unit }
+        controller.loadForComponent(TEST_COMPONENT, Consumer {})
 
         reset(persistenceWrapper)
         verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
                 capture(controlLoadCallbackCaptor))
 
-        controlLoadCallbackCaptor.value.invoke(listOf(control))
+        controlLoadCallbackCaptor.value.accept(listOf(control))
 
         verify(persistenceWrapper).storeFavorites(listOf(newControlInfo))
     }
@@ -294,19 +296,22 @@
         var loaded = false
         val control = builderFromInfo(TEST_CONTROL_INFO).build()
 
-        controller.loadForComponent(TEST_COMPONENT) { controls, favorites ->
+        controller.loadForComponent(TEST_COMPONENT, Consumer { data ->
+            val controls = data.allControls
+            val favorites = data.favoritesIds
             loaded = true
             assertEquals(1, controls.size)
             val controlStatus = controls[0]
             assertEquals(ControlStatus(control, false), controlStatus)
 
             assertTrue(favorites.isEmpty())
-        }
+            assertFalse(data.errorOnLoad)
+        })
 
         verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
                 capture(controlLoadCallbackCaptor))
 
-        controlLoadCallbackCaptor.value.invoke(listOf(control))
+        controlLoadCallbackCaptor.value.accept(listOf(control))
 
         assertTrue(loaded)
     }
@@ -318,7 +323,9 @@
         val control2 = builderFromInfo(TEST_CONTROL_INFO_2).build()
         controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
 
-        controller.loadForComponent(TEST_COMPONENT) { controls, favorites ->
+        controller.loadForComponent(TEST_COMPONENT, Consumer { data ->
+            val controls = data.allControls
+            val favorites = data.favoritesIds
             loaded = true
             assertEquals(2, controls.size)
             val controlStatus = controls.first { it.control.controlId == TEST_CONTROL_ID }
@@ -329,12 +336,13 @@
 
             assertEquals(1, favorites.size)
             assertEquals(TEST_CONTROL_ID, favorites[0])
-        }
+            assertFalse(data.errorOnLoad)
+        })
 
         verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
                 capture(controlLoadCallbackCaptor))
 
-        controlLoadCallbackCaptor.value.invoke(listOf(control, control2))
+        controlLoadCallbackCaptor.value.accept(listOf(control, control2))
 
         assertTrue(loaded)
     }
@@ -344,7 +352,9 @@
         var loaded = false
         controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
 
-        controller.loadForComponent(TEST_COMPONENT) { controls, favorites ->
+        controller.loadForComponent(TEST_COMPONENT, Consumer { data ->
+            val controls = data.allControls
+            val favorites = data.favoritesIds
             loaded = true
             assertEquals(1, controls.size)
             val controlStatus = controls[0]
@@ -354,12 +364,41 @@
 
             assertEquals(1, favorites.size)
             assertEquals(TEST_CONTROL_ID, favorites[0])
-        }
+            assertFalse(data.errorOnLoad)
+        })
 
         verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
                 capture(controlLoadCallbackCaptor))
 
-        controlLoadCallbackCaptor.value.invoke(emptyList())
+        controlLoadCallbackCaptor.value.accept(emptyList())
+
+        assertTrue(loaded)
+    }
+
+    @Test
+    fun testErrorOnLoad_notRemoved() {
+        var loaded = false
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+
+        controller.loadForComponent(TEST_COMPONENT, Consumer { data ->
+            val controls = data.allControls
+            val favorites = data.favoritesIds
+            loaded = true
+            assertEquals(1, controls.size)
+            val controlStatus = controls[0]
+            assertEquals(TEST_CONTROL_ID, controlStatus.control.controlId)
+            assertTrue(controlStatus.favorite)
+            assertFalse(controlStatus.removed)
+
+            assertEquals(1, favorites.size)
+            assertEquals(TEST_CONTROL_ID, favorites[0])
+            assertTrue(data.errorOnLoad)
+        })
+
+        verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
+                capture(controlLoadCallbackCaptor))
+
+        controlLoadCallbackCaptor.value.error("")
 
         assertTrue(loaded)
     }
@@ -370,12 +409,12 @@
         val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2)
         val control = builderFromInfo(newControlInfo).build()
 
-        controller.loadForComponent(TEST_COMPONENT) { _, _ -> Unit }
+        controller.loadForComponent(TEST_COMPONENT, Consumer {})
 
         verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
                 capture(controlLoadCallbackCaptor))
 
-        controlLoadCallbackCaptor.value.invoke(listOf(control))
+        controlLoadCallbackCaptor.value.accept(listOf(control))
 
         val favorites = controller.getFavoriteControls()
         assertEquals(1, favorites.size)
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
index 40566dc..ddd6b12 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
@@ -18,7 +18,6 @@
 
 import android.content.ComponentName
 import android.os.UserHandle
-import android.service.controls.Control
 import android.service.controls.IControlsActionCallback
 import android.service.controls.IControlsLoadCallback
 import android.service.controls.IControlsProvider
@@ -28,7 +27,6 @@
 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
@@ -41,6 +39,7 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.anyString
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Captor
 import org.mockito.Mock
@@ -53,20 +52,22 @@
 class ControlsProviderLifecycleManagerTest : SysuiTestCase() {
 
     @Mock
-    private lateinit var actionCallback: IControlsActionCallback.Stub
+    private lateinit var actionCallbackService: IControlsActionCallback.Stub
     @Mock
-    private lateinit var loadCallback: IControlsLoadCallback.Stub
+    private lateinit var loadCallbackService: IControlsLoadCallback.Stub
     @Mock
-    private lateinit var subscriber: IControlsSubscriber.Stub
+    private lateinit var subscriberService: IControlsSubscriber.Stub
     @Mock
     private lateinit var service: IControlsProvider.Stub
+    @Mock
+    private lateinit var loadCallback: ControlsBindingController.LoadCallback
 
     @Captor
     private lateinit var wrapperCaptor: ArgumentCaptor<ControlActionWrapper>
 
     private val componentName = ComponentName("test.pkg", "test.cls")
     private lateinit var manager: ControlsProviderLifecycleManager
-    private lateinit var executor: DelayableExecutor
+    private lateinit var executor: FakeExecutor
 
     companion object {
         fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
@@ -84,9 +85,9 @@
         manager = ControlsProviderLifecycleManager(
                 context,
                 executor,
-                loadCallback,
-                actionCallback,
-                subscriber,
+                loadCallbackService,
+                actionCallbackService,
+                subscriberService,
                 UserHandle.of(0),
                 componentName
         )
@@ -112,18 +113,17 @@
 
     @Test
     fun testMaybeBindAndLoad() {
-        val callback: (List<Control>) -> Unit = {}
-        manager.maybeBindAndLoad(callback)
+        manager.maybeBindAndLoad(loadCallback)
 
-        verify(service).load(loadCallback)
+        verify(service).load(loadCallbackService)
 
         assertTrue(mContext.isBound(componentName))
-        assertEquals(callback, manager.lastLoadCallback)
+        assertEquals(loadCallback, manager.lastLoadCallback)
     }
 
     @Test
     fun testMaybeUnbind_bindingAndCallback() {
-        manager.maybeBindAndLoad {}
+        manager.maybeBindAndLoad(loadCallback)
 
         manager.unbindService()
         assertFalse(mContext.isBound(componentName))
@@ -131,12 +131,22 @@
     }
 
     @Test
+    fun testMaybeBindAndLoad_timeout() {
+        manager.maybeBindAndLoad(loadCallback)
+
+        executor.advanceClockToLast()
+        executor.runAllReady()
+
+        verify(loadCallback).error(anyString())
+    }
+
+    @Test
     fun testMaybeBindAndSubscribe() {
         val list = listOf("TEST_ID")
         manager.maybeBindAndSubscribe(list)
 
         assertTrue(mContext.isBound(componentName))
-        verify(service).subscribe(list, subscriber)
+        verify(service).subscribe(list, subscriberService)
     }
 
     @Test
@@ -147,7 +157,7 @@
 
         assertTrue(mContext.isBound(componentName))
         verify(service).action(eq(controlId), capture(wrapperCaptor),
-                eq(actionCallback))
+                eq(actionCallbackService))
         assertEquals(action, wrapperCaptor.getValue().getWrappedAction())
     }
 }
\ 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 96db16a..12e9d31 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
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.collection;
 
+import static android.app.Notification.FLAG_NO_CLEAR;
 import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
 import static android.service.notification.NotificationListenerService.REASON_CANCEL;
 import static android.service.notification.NotificationListenerService.REASON_CLICK;
@@ -33,8 +34,8 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyObject;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
@@ -50,12 +51,12 @@
 import android.os.RemoteException;
 import android.service.notification.NotificationListenerService.Ranking;
 import android.service.notification.NotificationListenerService.RankingMap;
-import android.service.notification.NotificationStats;
 import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.Pair;
 
 import androidx.test.filters.SmallTest;
 
@@ -104,7 +105,6 @@
     @Spy private RecordingCollectionListener mCollectionListener;
     @Mock private CollectionReadyForBuildListener mBuildListener;
     @Mock private FeatureFlags mFeatureFlags;
-    @Mock private DismissedByUserStats mDismissedByUserStats;
 
     @Spy private RecordingLifetimeExtender mExtender1 = new RecordingLifetimeExtender("Extender1");
     @Spy private RecordingLifetimeExtender mExtender2 = new RecordingLifetimeExtender("Extender2");
@@ -424,13 +424,16 @@
     public void testDismissingLifetimeExtendedSummaryDoesNotDismissChildren() {
         // GIVEN A notif group with one summary and two children
         mCollection.addNotificationLifetimeExtender(mExtender1);
-        NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 1, "myTag")
-                .setGroup(mContext, GROUP_1)
-                .setGroupSummary(mContext, true));
-        NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 2, "myTag")
-                .setGroup(mContext, GROUP_1));
-        NotifEvent notif3 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3, "myTag")
-                .setGroup(mContext, GROUP_1));
+        CollectionEvent notif1 = postNotif(
+                buildNotif(TEST_PACKAGE, 1, "myTag")
+                        .setGroup(mContext, GROUP_1)
+                        .setGroupSummary(mContext, true));
+        CollectionEvent notif2 = postNotif(
+                buildNotif(TEST_PACKAGE, 2, "myTag")
+                        .setGroup(mContext, GROUP_1));
+        CollectionEvent notif3 = postNotif(
+                buildNotif(TEST_PACKAGE, 3, "myTag")
+                        .setGroup(mContext, GROUP_1));
 
         NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
         NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
@@ -456,7 +459,7 @@
     }
 
     @Test
-    public void testDismissInterceptorsAreCalled() throws RemoteException {
+    public void testDismissNotificationCallsDismissInterceptors() throws RemoteException {
         // GIVEN a collection with notifications with multiple dismiss interceptors
         mInterceptor1.shouldInterceptDismissal = true;
         mInterceptor2.shouldInterceptDismissal = true;
@@ -469,10 +472,7 @@
         NotificationEntry entry = mCollectionListener.getEntry(notif.key);
 
         // WHEN a notification is manually dismissed
-        DismissedByUserStats stats = new DismissedByUserStats(
-                NotificationStats.DISMISSAL_SHADE,
-                NotificationStats.DISMISS_SENTIMENT_NEUTRAL,
-                NotificationVisibility.obtain(entry.getKey(), 7, 2, true));
+        DismissedByUserStats stats = defaultStats(entry);
         mCollection.dismissNotification(entry, stats);
 
         // THEN all interceptors get checked
@@ -506,10 +506,7 @@
         NotificationEntry entry = mCollectionListener.getEntry(notif.key);
 
         // WHEN a notification is manually dismissed and intercepted
-        DismissedByUserStats stats = new DismissedByUserStats(
-                NotificationStats.DISMISSAL_SHADE,
-                NotificationStats.DISMISS_SENTIMENT_NEUTRAL,
-                NotificationVisibility.obtain(entry.getKey(), 7, 2, true));
+        DismissedByUserStats stats = defaultStats(entry);
         mCollection.dismissNotification(entry, stats);
         assertEquals(List.of(mInterceptor1, mInterceptor2), entry.mDismissInterceptors);
         clearInvocations(mInterceptor1, mInterceptor2);
@@ -531,7 +528,7 @@
                 eq(notif.sbn.getKey()),
                 anyInt(),
                 anyInt(),
-                anyObject());
+                eq(stats.notificationVisibility));
     }
 
     @Test
@@ -544,19 +541,16 @@
         NotificationEntry entry = mCollectionListener.getEntry(notif.key);
 
         // GIVEN a notification is manually dismissed
-        DismissedByUserStats stats = new DismissedByUserStats(
-                NotificationStats.DISMISSAL_SHADE,
-                NotificationStats.DISMISS_SENTIMENT_NEUTRAL,
-                NotificationVisibility.obtain(entry.getKey(), 7, 2, true));
+        DismissedByUserStats stats = defaultStats(entry);
         mCollection.dismissNotification(entry, stats);
 
         // WHEN all interceptors end their interception dismissal
         mInterceptor1.shouldInterceptDismissal = false;
         mInterceptor1.onEndInterceptionCallback.onEndDismissInterception(mInterceptor1, entry,
-                mDismissedByUserStats);
+                stats);
 
         // THEN we send the dismissal to system server
-        verify(mStatusBarService, times(1)).onNotificationClear(
+        verify(mStatusBarService).onNotificationClear(
                 eq(notif.sbn.getPackageName()),
                 eq(notif.sbn.getTag()),
                 eq(47),
@@ -564,7 +558,7 @@
                 eq(notif.sbn.getKey()),
                 anyInt(),
                 anyInt(),
-                anyObject());
+                eq(stats.notificationVisibility));
     }
 
     @Test
@@ -581,16 +575,12 @@
         NotificationEntry entry = mCollectionListener.getEntry(notif.key);
 
         // GIVEN a notification is manually dismissed
-        DismissedByUserStats stats = new DismissedByUserStats(
-                NotificationStats.DISMISSAL_SHADE,
-                NotificationStats.DISMISS_SENTIMENT_NEUTRAL,
-                NotificationVisibility.obtain(entry.getKey(), 7, 2, true));
-        mCollection.dismissNotification(entry, stats);
+        mCollection.dismissNotification(entry, defaultStats(entry));
 
        // WHEN an interceptor ends its interception
         mInterceptor1.shouldInterceptDismissal = false;
         mInterceptor1.onEndInterceptionCallback.onEndDismissInterception(mInterceptor1, entry,
-                mDismissedByUserStats);
+                defaultStats(entry));
 
         // THEN all interceptors get checked
         verify(mInterceptor1).shouldInterceptDismissal(entry);
@@ -613,7 +603,7 @@
 
         // WHEN we try to end the dismissal of an interceptor that didn't intercept the notif
         mInterceptor1.onEndInterceptionCallback.onEndDismissInterception(mInterceptor1, entry,
-                mDismissedByUserStats);
+                defaultStats(entry));
 
         // THEN an exception is thrown
     }
@@ -636,11 +626,11 @@
     @Test
     public void testGroupChildrenAreDismissedLocallyWhenSummaryIsDismissed() {
         // GIVEN a collection with two grouped notifs in it
-        NotifEvent notif0 = mNoMan.postNotif(
+        CollectionEvent notif0 = postNotif(
                 buildNotif(TEST_PACKAGE, 0)
                         .setGroup(mContext, GROUP_1)
                         .setGroupSummary(mContext, true));
-        NotifEvent notif1 = mNoMan.postNotif(
+        CollectionEvent notif1 = postNotif(
                 buildNotif(TEST_PACKAGE, 1)
                         .setGroup(mContext, GROUP_1));
         NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key);
@@ -657,11 +647,11 @@
     @Test
     public void testUpdatingDismissedSummaryBringsChildrenBack() {
         // GIVEN a collection with two grouped notifs in it
-        NotifEvent notif0 = mNoMan.postNotif(
+        CollectionEvent notif0 = postNotif(
                 buildNotif(TEST_PACKAGE, 0)
                         .setGroup(mContext, GROUP_1)
                         .setGroupSummary(mContext, true));
-        NotifEvent notif1 = mNoMan.postNotif(
+        CollectionEvent notif1 = postNotif(
                 buildNotif(TEST_PACKAGE, 1)
                         .setGroup(mContext, GROUP_1));
         NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key);
@@ -680,14 +670,14 @@
     @Test
     public void testDismissedChildrenAreNotResetByParentUpdate() {
         // GIVEN a collection with three grouped notifs in it
-        NotifEvent notif0 = mNoMan.postNotif(
+        CollectionEvent notif0 = postNotif(
                 buildNotif(TEST_PACKAGE, 0)
                         .setGroup(mContext, GROUP_1)
                         .setGroupSummary(mContext, true));
-        NotifEvent notif1 = mNoMan.postNotif(
+        CollectionEvent notif1 = postNotif(
                 buildNotif(TEST_PACKAGE, 1)
                         .setGroup(mContext, GROUP_1));
-        NotifEvent notif2 = mNoMan.postNotif(
+        CollectionEvent notif2 = postNotif(
                 buildNotif(TEST_PACKAGE, 2)
                         .setGroup(mContext, GROUP_1));
         NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key);
@@ -709,11 +699,11 @@
     @Test
     public void testUpdatingGroupKeyOfDismissedSummaryBringsChildrenBack() {
         // GIVEN a collection with two grouped notifs in it
-        NotifEvent notif0 = mNoMan.postNotif(
+        CollectionEvent notif0 = postNotif(
                 buildNotif(TEST_PACKAGE, 0)
                         .setOverrideGroupKey(GROUP_1)
                         .setGroupSummary(mContext, true));
-        NotifEvent notif1 = mNoMan.postNotif(
+        CollectionEvent notif1 = postNotif(
                 buildNotif(TEST_PACKAGE, 1)
                         .setOverrideGroupKey(GROUP_1));
         NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key);
@@ -1055,6 +1045,213 @@
         assertEquals(REASON_NOT_CANCELED, entry0.mCancellationReason);
     }
 
+    @Test
+    public void testDismissNotificationsRebuildsOnce() {
+        // GIVEN a collection with a couple notifications
+        NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+        NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+        NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+        NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+        clearInvocations(mBuildListener);
+
+        // WHEN both notifications are manually dismissed together
+        mCollection.dismissNotifications(
+                List.of(new Pair(entry1, defaultStats(entry1)),
+                        new Pair(entry2, defaultStats(entry2))));
+
+        // THEN build list is only called one time
+        verify(mBuildListener).onBuildList(any(Collection.class));
+    }
+
+    @Test
+    public void testDismissNotificationsSentToSystemServer() throws RemoteException {
+        // GIVEN a collection with a couple notifications
+        NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+        NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+        NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+        NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+        // WHEN both notifications are manually dismissed together
+        DismissedByUserStats stats1 = defaultStats(entry1);
+        DismissedByUserStats stats2 = defaultStats(entry2);
+        mCollection.dismissNotifications(
+                List.of(new Pair(entry1, defaultStats(entry1)),
+                        new Pair(entry2, defaultStats(entry2))));
+
+        // THEN we send the dismissals to system server
+        verify(mStatusBarService).onNotificationClear(
+                notif1.sbn.getPackageName(),
+                notif1.sbn.getTag(),
+                47,
+                notif1.sbn.getUser().getIdentifier(),
+                notif1.sbn.getKey(),
+                stats1.dismissalSurface,
+                stats1.dismissalSentiment,
+                stats1.notificationVisibility);
+
+        verify(mStatusBarService).onNotificationClear(
+                notif2.sbn.getPackageName(),
+                notif2.sbn.getTag(),
+                88,
+                notif2.sbn.getUser().getIdentifier(),
+                notif2.sbn.getKey(),
+                stats2.dismissalSurface,
+                stats2.dismissalSentiment,
+                stats2.notificationVisibility);
+    }
+
+    @Test
+    public void testDismissNotificationsMarkedAsDismissed() {
+        // GIVEN a collection with a couple notifications
+        NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+        NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+        NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+        NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+        // WHEN both notifications are manually dismissed together
+        mCollection.dismissNotifications(
+                List.of(new Pair(entry1, defaultStats(entry1)),
+                        new Pair(entry2, defaultStats(entry2))));
+
+        // THEN the entries are marked as dismissed
+        assertEquals(DISMISSED, entry1.getDismissState());
+        assertEquals(DISMISSED, entry2.getDismissState());
+    }
+
+    @Test
+    public void testDismissNotificationssCallsDismissInterceptors() {
+        // GIVEN a collection with notifications with multiple dismiss interceptors
+        mInterceptor1.shouldInterceptDismissal = true;
+        mInterceptor2.shouldInterceptDismissal = true;
+        mInterceptor3.shouldInterceptDismissal = false;
+        mCollection.addNotificationDismissInterceptor(mInterceptor1);
+        mCollection.addNotificationDismissInterceptor(mInterceptor2);
+        mCollection.addNotificationDismissInterceptor(mInterceptor3);
+
+        NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+        NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+        NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+        NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+        // WHEN both notifications are manually dismissed together
+        mCollection.dismissNotifications(
+                List.of(new Pair(entry1, defaultStats(entry1)),
+                        new Pair(entry2, defaultStats(entry2))));
+
+        // THEN all interceptors get checked
+        verify(mInterceptor1).shouldInterceptDismissal(entry1);
+        verify(mInterceptor2).shouldInterceptDismissal(entry1);
+        verify(mInterceptor3).shouldInterceptDismissal(entry1);
+        verify(mInterceptor1).shouldInterceptDismissal(entry2);
+        verify(mInterceptor2).shouldInterceptDismissal(entry2);
+        verify(mInterceptor3).shouldInterceptDismissal(entry2);
+
+        assertEquals(List.of(mInterceptor1, mInterceptor2), entry1.mDismissInterceptors);
+        assertEquals(List.of(mInterceptor1, mInterceptor2), entry2.mDismissInterceptors);
+    }
+
+    @Test
+    public void testDismissAllNotificationsCallsRebuildOnce() {
+        // GIVEN a collection with a couple notifications
+        NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+        NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+        NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+        NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+        clearInvocations(mBuildListener);
+
+        // WHEN all notifications are dismissed for the user who posted both notifs
+        mCollection.dismissAllNotifications(entry1.getSbn().getUser().getIdentifier());
+
+        // THEN build list is only called one time
+        verify(mBuildListener).onBuildList(any(Collection.class));
+    }
+
+    @Test
+    public void testDismissAllNotificationsSentToSystemServer() throws RemoteException {
+        // GIVEN a collection with a couple notifications
+        NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+        NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+        NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+        NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+        // WHEN all notifications are dismissed for the user who posted both notifs
+        mCollection.dismissAllNotifications(entry1.getSbn().getUser().getIdentifier());
+
+        // THEN we send the dismissal to system server
+        verify(mStatusBarService).onClearAllNotifications(
+                entry1.getSbn().getUser().getIdentifier());
+    }
+
+    @Test
+    public void testDismissAllNotificationsMarkedAsDismissed() {
+        // GIVEN a collection with a couple notifications
+        NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+        NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+        NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+        NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+        // WHEN all notifications are dismissed for the user who posted both notifs
+        mCollection.dismissAllNotifications(entry1.getSbn().getUser().getIdentifier());
+
+        // THEN the entries are marked as dismissed
+        assertEquals(DISMISSED, entry1.getDismissState());
+        assertEquals(DISMISSED, entry2.getDismissState());
+    }
+
+    @Test
+    public void testDismissAllNotificationsDoesNotMarkDismissedUnclearableNotifs() {
+        // GIVEN a collection with one unclearable notification and one clearable notification
+        NotificationEntryBuilder notifEntryBuilder = buildNotif(TEST_PACKAGE, 47, "myTag");
+        notifEntryBuilder.modifyNotification(mContext)
+                .setFlag(FLAG_NO_CLEAR, true);
+        NotifEvent unclearabeNotif = mNoMan.postNotif(notifEntryBuilder);
+        NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+        NotificationEntry unclearableEntry = mCollectionListener.getEntry(unclearabeNotif.key);
+        NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+        // WHEN all notifications are dismissed for the user who posted both notifs
+        mCollection.dismissAllNotifications(unclearableEntry.getSbn().getUser().getIdentifier());
+
+        // THEN only the clearable entry is marked as dismissed
+        assertEquals(NOT_DISMISSED, unclearableEntry.getDismissState());
+        assertEquals(DISMISSED, entry2.getDismissState());
+    }
+
+    @Test
+    public void testDismissAllNotificationsCallsDismissInterceptorsOnlyOnUnclearableNotifs() {
+        // GIVEN a collection with multiple dismiss interceptors
+        mInterceptor1.shouldInterceptDismissal = true;
+        mInterceptor2.shouldInterceptDismissal = true;
+        mInterceptor3.shouldInterceptDismissal = false;
+        mCollection.addNotificationDismissInterceptor(mInterceptor1);
+        mCollection.addNotificationDismissInterceptor(mInterceptor2);
+        mCollection.addNotificationDismissInterceptor(mInterceptor3);
+
+        // GIVEN a collection with one unclearable and one clearable notification
+        NotifEvent unclearableNotif = mNoMan.postNotif(
+                buildNotif(TEST_PACKAGE, 47, "myTag")
+                        .setFlag(mContext, FLAG_NO_CLEAR, true));
+        NotificationEntry unclearable = mCollectionListener.getEntry(unclearableNotif.key);
+        NotifEvent clearableNotif = mNoMan.postNotif(
+                buildNotif(TEST_PACKAGE, 88, "myTag")
+                        .setFlag(mContext, FLAG_NO_CLEAR, false));
+        NotificationEntry clearable = mCollectionListener.getEntry(clearableNotif.key);
+
+        // WHEN all notifications are dismissed for the user who posted the notif
+        mCollection.dismissAllNotifications(clearable.getSbn().getUser().getIdentifier());
+
+        // THEN all interceptors get checked for the unclearable notification
+        verify(mInterceptor1).shouldInterceptDismissal(unclearable);
+        verify(mInterceptor2).shouldInterceptDismissal(unclearable);
+        verify(mInterceptor3).shouldInterceptDismissal(unclearable);
+        assertEquals(List.of(mInterceptor1, mInterceptor2), unclearable.mDismissInterceptors);
+
+        // THEN no interceptors get checked for the clearable notification
+        verify(mInterceptor1, never()).shouldInterceptDismissal(clearable);
+        verify(mInterceptor2, never()).shouldInterceptDismissal(clearable);
+        verify(mInterceptor3, never()).shouldInterceptDismissal(clearable);
+    }
+
     private static NotificationEntryBuilder buildNotif(String pkg, int id, String tag) {
         return new NotificationEntryBuilder()
                 .setPkg(pkg)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index b16e52c..9ccee75 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -70,6 +70,8 @@
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
 import com.android.systemui.statusbar.notification.TestableNotificationEntryManager;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
@@ -133,6 +135,7 @@
     @Mock private NotificationSectionsManager mNotificationSectionsManager;
     @Mock private NotificationSection mNotificationSection;
     @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
+    @Mock private FeatureFlags mFeatureFlags;
     private UserChangedListener mUserChangedListener;
     private TestableNotificationEntryManager mEntryManager;
     private int mOriginalInterruptionModelSetting;
@@ -182,9 +185,8 @@
                 mock(LeakDetector.class),
                 mock(ForegroundServiceDismissalFeatureController.class)
         );
-        mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
         mEntryManager.setUpForTest(mock(NotificationPresenter.class), null, mHeadsUpManager);
-
+        when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(false);
 
         NotificationShelf notificationShelf = mock(NotificationShelf.class);
         when(mNotificationSectionsManager.createSectionsForBuckets()).thenReturn(
@@ -208,7 +210,11 @@
                 mZenModeController,
                 mNotificationSectionsManager,
                 mock(ForegroundServiceSectionController.class),
-                mock(ForegroundServiceDismissalFeatureController.class)
+                mock(ForegroundServiceDismissalFeatureController.class),
+                mFeatureFlags,
+                mock(NotifPipeline.class),
+                mEntryManager,
+                mock(NotifCollection.class)
         );
         verify(mLockscreenUserManager).addUserChangedListener(userChangedCaptor.capture());
         mUserChangedListener = userChangedCaptor.getValue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerTest.java
index 40d3395..7d52df7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerTest.java
@@ -34,7 +34,6 @@
 import com.android.internal.colorextraction.ColorExtractor;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.statusbar.SuperStatusBarViewFactory;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
@@ -52,14 +51,13 @@
 
     @Mock private WindowManager mWindowManager;
     @Mock private DozeParameters mDozeParameters;
-    @Mock private NotificationShadeWindowView mStatusBarView;
+    @Mock private NotificationShadeWindowView mNotificationShadeWindowView;
     @Mock private IActivityManager mActivityManager;
     @Mock private SysuiStatusBarStateController mStatusBarStateController;
     @Mock private ConfigurationController mConfigurationController;
     @Mock private KeyguardBypassController mKeyguardBypassController;
     @Mock private SysuiColorExtractor mColorExtractor;
     @Mock ColorExtractor.GradientColors mGradientColors;
-    @Mock private SuperStatusBarViewFactory mSuperStatusBarViewFactory;
 
     private NotificationShadeWindowController mNotificationShadeWindowController;
 
@@ -68,13 +66,11 @@
         MockitoAnnotations.initMocks(this);
         when(mDozeParameters.getAlwaysOn()).thenReturn(true);
         when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
-        when(mSuperStatusBarViewFactory.getNotificationShadeWindowView())
-                .thenReturn(mStatusBarView);
 
         mNotificationShadeWindowController = new NotificationShadeWindowController(mContext,
                 mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
-                mConfigurationController, mKeyguardBypassController, mColorExtractor,
-                mSuperStatusBarViewFactory);
+                mConfigurationController, mKeyguardBypassController, mColorExtractor);
+        mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView);
 
         mNotificationShadeWindowController.attach();
     }
@@ -104,7 +100,7 @@
 
     @Test
     public void testAdd_updatesVisibilityFlags() {
-        verify(mStatusBarView).setSystemUiVisibility(anyInt());
+        verify(mNotificationShadeWindowView).setSystemUiVisibility(anyInt());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 5027610..1e4df27 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -58,6 +58,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -67,6 +68,8 @@
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
@@ -114,6 +117,12 @@
     private BubbleController mBubbleController;
     @Mock
     private ShadeControllerImpl mShadeController;
+    @Mock
+    private FeatureFlags mFeatureFlags;
+    @Mock
+    private NotifPipeline mNotifPipeline;
+    @Mock
+    private NotifCollection mNotifCollection;
 
     @Mock
     private ActivityIntentHelper mActivityIntentHelper;
@@ -162,6 +171,7 @@
         mActiveNotifications.add(mBubbleNotificationRow.getEntry());
         when(mEntryManager.getVisibleNotifications()).thenReturn(mActiveNotifications);
         when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
+        when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(false);
 
         mNotificationActivityStarter = (new StatusBarNotificationActivityStarter.Builder(
                 getContext(), mock(CommandQueue.class), () -> mAssistManager,
@@ -175,11 +185,12 @@
                 mKeyguardStateController,
                 mock(NotificationInterruptionStateProvider.class), mock(MetricsLogger.class),
                 mock(LockPatternUtils.class), mHandler, mHandler, mUiBgExecutor,
-                mActivityIntentHelper, mBubbleController, mShadeController))
+                mActivityIntentHelper, mBubbleController, mShadeController, mFeatureFlags,
+                mNotifPipeline, mNotifCollection)
                 .setStatusBar(mStatusBar)
                 .setNotificationPanelViewController(mock(NotificationPanelViewController.class))
                 .setNotificationPresenter(mock(NotificationPresenter.class))
-                .setActivityLaunchAnimator(mock(ActivityLaunchAnimator.class))
+                .setActivityLaunchAnimator(mock(ActivityLaunchAnimator.class)))
         .build();
 
         // set up dismissKeyguardThenExecute to synchronously invoke the OnDismissAction arg
diff --git a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
new file mode 100644
index 0000000..d58f2c9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
@@ -0,0 +1,207 @@
+/*
+ * 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.toast;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.INotificationManager;
+import android.app.ITransientNotificationCallback;
+import android.os.Binder;
+import android.testing.AndroidTestingRunner;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.CommandQueue;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ToastUITest extends SysuiTestCase {
+    private static final String PACKAGE_NAME_1 = "com.example1.test";
+    private static final Binder TOKEN_1 = new Binder();
+    private static final Binder WINDOW_TOKEN_1 = new Binder();
+    private static final String PACKAGE_NAME_2 = "com.example2.test";
+    private static final Binder TOKEN_2 = new Binder();
+    private static final Binder WINDOW_TOKEN_2 = new Binder();
+    private static final String TEXT = "Hello World";
+    private static final int MESSAGE_RES_ID = R.id.message;
+
+    @Mock private CommandQueue mCommandQueue;
+    @Mock private WindowManager mWindowManager;
+    @Mock private INotificationManager mNotificationManager;
+    @Mock private AccessibilityManager mAccessibilityManager;
+    @Mock private ITransientNotificationCallback mCallback;
+    @Captor private ArgumentCaptor<View> mViewCaptor;
+    @Captor private ArgumentCaptor<ViewGroup.LayoutParams> mParamsCaptor;
+    private ToastUI mToastUI;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mToastUI = new ToastUI(mContext, mCommandQueue, mWindowManager, mNotificationManager,
+                mAccessibilityManager);
+    }
+
+    @Test
+    public void testStart_addToastUIAsCallbackToCommandQueue() throws Exception {
+        mToastUI.start();
+
+        verify(mCommandQueue).addCallback(mToastUI);
+    }
+
+    @Test
+    public void testShowToast_addsCorrectViewToWindowManager() throws Exception {
+        mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG, null);
+
+        verify(mWindowManager).addView(mViewCaptor.capture(), any());
+        View view = mViewCaptor.getValue();
+        assertThat(((TextView) view.findViewById(MESSAGE_RES_ID)).getText()).isEqualTo(TEXT);
+    }
+
+    @Test
+    public void testShowToast_addsViewWithCorrectLayoutParamsToWindowManager() throws Exception {
+        mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG, null);
+
+        verify(mWindowManager).addView(any(), mParamsCaptor.capture());
+        ViewGroup.LayoutParams params = mParamsCaptor.getValue();
+        assertThat(params).isInstanceOf(WindowManager.LayoutParams.class);
+        WindowManager.LayoutParams windowParams = (WindowManager.LayoutParams) params;
+        assertThat(windowParams.packageName).isEqualTo(mContext.getPackageName());
+        assertThat(windowParams.getTitle()).isEqualTo("Toast");
+        assertThat(windowParams.token).isEqualTo(WINDOW_TOKEN_1);
+    }
+
+    @Test
+    public void testShowToast_callsCallback() throws Exception {
+        mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
+                mCallback);
+
+        verify(mCallback).onToastShown();
+    }
+
+    @Test
+    public void testShowToast_sendsAccessibilityEvent() throws Exception {
+        when(mAccessibilityManager.isEnabled()).thenReturn(true);
+
+        mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG, null);
+
+        ArgumentCaptor<AccessibilityEvent> eventCaptor = ArgumentCaptor.forClass(
+                AccessibilityEvent.class);
+        verify(mAccessibilityManager).sendAccessibilityEvent(eventCaptor.capture());
+        AccessibilityEvent event = eventCaptor.getValue();
+        assertThat(event.getEventType()).isEqualTo(
+                AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
+        assertThat(event.getClassName()).isEqualTo(Toast.class.getName());
+        assertThat(event.getPackageName()).isEqualTo(PACKAGE_NAME_1);
+    }
+
+    @Test
+    public void testHideToast_removesView() throws Exception {
+        mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
+                mCallback);
+        View view = verifyWmAddViewAndAttachToParent();
+
+        mToastUI.hideToast(PACKAGE_NAME_1, TOKEN_1);
+
+        verify(mWindowManager).removeViewImmediate(view);
+    }
+
+    @Test
+    public void testHideToast_finishesToken() throws Exception {
+        mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
+                mCallback);
+
+        mToastUI.hideToast(PACKAGE_NAME_1, TOKEN_1);
+
+        verify(mNotificationManager).finishToken(PACKAGE_NAME_1, WINDOW_TOKEN_1);
+    }
+
+    @Test
+    public void testHideToast_callsCallback() throws Exception {
+        mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
+                mCallback);
+
+        mToastUI.hideToast(PACKAGE_NAME_1, TOKEN_1);
+
+        verify(mCallback).onToastHidden();
+    }
+
+    @Test
+    public void testHideToast_whenNotCurrentToastToken_doesNotHideToast() throws Exception {
+        mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
+                mCallback);
+
+        mToastUI.hideToast(PACKAGE_NAME_1, TOKEN_2);
+
+        verify(mCallback, never()).onToastHidden();
+    }
+
+    @Test
+    public void testHideToast_whenNotCurrentToastPackage_doesNotHideToast() throws Exception {
+        mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
+                mCallback);
+
+        mToastUI.hideToast(PACKAGE_NAME_2, TOKEN_1);
+
+        verify(mCallback, never()).onToastHidden();
+    }
+
+    @Test
+    public void testShowToast_afterShowToast_hidesCurrentToast() throws Exception {
+        mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
+                mCallback);
+        View view = verifyWmAddViewAndAttachToParent();
+
+        mToastUI.showToast(PACKAGE_NAME_2, TOKEN_2, TEXT, WINDOW_TOKEN_2, Toast.LENGTH_LONG, null);
+
+        verify(mWindowManager).removeViewImmediate(view);
+        verify(mNotificationManager).finishToken(PACKAGE_NAME_1, WINDOW_TOKEN_1);
+        verify(mCallback).onToastHidden();
+    }
+
+    private View verifyWmAddViewAndAttachToParent() {
+        ArgumentCaptor<View> viewCaptor = ArgumentCaptor.forClass(View.class);
+        verify(mWindowManager).addView(viewCaptor.capture(), any());
+        View view = viewCaptor.getValue();
+        // Simulate attaching to view hierarchy
+        ViewGroup parent = new FrameLayout(mContext);
+        parent.addView(view);
+        return view;
+    }
+}
diff --git a/packages/services/PacProcessor/jni/Android.bp b/packages/services/PacProcessor/jni/Android.bp
index ab21a76..351e92c 100644
--- a/packages/services/PacProcessor/jni/Android.bp
+++ b/packages/services/PacProcessor/jni/Android.bp
@@ -37,7 +37,8 @@
         "-Wunused",
         "-Wunreachable-code",
     ],
-    sanitize: {
-        cfi: true,
-    },
+    // Re-enable when b/145990493 is fixed
+    // sanitize: {
+    //    cfi: true,
+    // },
 }
diff --git a/packages/services/PacProcessor/src/com/android/pacprocessor/PacWebView.java b/packages/services/PacProcessor/src/com/android/pacprocessor/PacWebView.java
index 89c4665..4dd00f1 100644
--- a/packages/services/PacProcessor/src/com/android/pacprocessor/PacWebView.java
+++ b/packages/services/PacProcessor/src/com/android/pacprocessor/PacWebView.java
@@ -19,8 +19,6 @@
 import android.util.Log;
 import android.webkit.PacProcessor;
 
-import com.android.internal.annotations.GuardedBy;
-
 /**
  * @hide
  */
@@ -28,10 +26,6 @@
     private static final String TAG = "PacWebView";
 
     private static final PacWebView sInstance = new PacWebView();
-
-    private Object mLock = new Object();
-
-    @GuardedBy("mLock")
     private PacProcessor mProcessor = PacProcessor.getInstance();
 
     public static PacWebView getInstance() {
@@ -39,20 +33,16 @@
     }
 
     @Override
-    public boolean setCurrentProxyScript(String script) {
-        synchronized (mLock) {
-            if (!mProcessor.setProxyScript(script)) {
-                Log.e(TAG, "Unable to parse proxy script.");
-                return false;
-            }
-            return true;
+    public synchronized boolean setCurrentProxyScript(String script) {
+        if (!mProcessor.setProxyScript(script)) {
+            Log.e(TAG, "Unable to parse proxy script.");
+            return false;
         }
+        return true;
     }
 
     @Override
-    public String makeProxyRequest(String url, String host) {
-        synchronized (mLock) {
-            return mProcessor.findProxyForUrl(url);
-        }
+    public synchronized String makeProxyRequest(String url, String host) {
+        return mProcessor.findProxyForUrl(url);
     }
 }
diff --git a/services/core/java/android/app/usage/UsageStatsManagerInternal.java b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
index 5c2cbfa..442c9e5 100644
--- a/services/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -220,9 +220,13 @@
      *     result.
      * @param hideShortcutInvocationEvents whether the {@link UsageEvents.Event#SHORTCUT_INVOCATION}
      *     events need to be excluded from the result.
+     * @param hideLocusIdEvents whether the {@link UsageEvents.Event#LOCUS_ID_SET}
+     *     events need to be excluded from the result.
+     *
      */
     public abstract UsageEvents queryEventsForUser(@UserIdInt int userId, long beginTime,
-            long endTime, boolean obfuscateInstantApps, boolean hideShortcutInvocationEvents);
+            long endTime, boolean obfuscateInstantApps, boolean hideShortcutInvocationEvents,
+            boolean hideLocusIdEvents);
 
     /**
      * Used to persist the last time a job was run for this app, in order to make decisions later
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index c47cde3..b334b26 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -3749,6 +3749,7 @@
             if (nm == null) return;
 
             if (request == CaptivePortal.APP_REQUEST_REEVALUATION_REQUIRED) {
+                checkNetworkStackPermission();
                 nm.forceReevaluation(Binder.getCallingUid());
             }
         }
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index c987620..9540f43 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -1556,16 +1556,16 @@
         }
 
         Objects.requireNonNull(callingPackage, "Null calling package cannot create IpSec tunnels");
-        switch (getAppOpsManager().noteOp(TUNNEL_OP, Binder.getCallingUid(), callingPackage)) {
-            case AppOpsManager.MODE_DEFAULT:
-                mContext.enforceCallingOrSelfPermission(
-                        android.Manifest.permission.MANAGE_IPSEC_TUNNELS, "IpSecService");
-                break;
-            case AppOpsManager.MODE_ALLOWED:
-                return;
-            default:
-                throw new SecurityException("Request to ignore AppOps for non-legacy API");
+
+        // OP_MANAGE_IPSEC_TUNNELS will return MODE_ERRORED by default, including for the system
+        // server. If the appop is not granted, require that the caller has the MANAGE_IPSEC_TUNNELS
+        // permission or is the System Server.
+        if (AppOpsManager.MODE_ALLOWED == getAppOpsManager().noteOpNoThrow(
+                TUNNEL_OP, Binder.getCallingUid(), callingPackage)) {
+            return;
         }
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.MANAGE_IPSEC_TUNNELS, "IpSecService");
     }
 
     private void createOrUpdateTransform(
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index c9ba988..b5b22f1 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -1300,13 +1300,6 @@
                     vol.state = newState;
                     onVolumeStateChangedLocked(vol, oldState, newState);
                 }
-                try {
-                    if (vol.type == VolumeInfo.TYPE_PRIVATE && state == VolumeInfo.STATE_MOUNTED) {
-                        mInstaller.onPrivateVolumeMounted(vol.getFsUuid());
-                    }
-                } catch (Installer.InstallerException e) {
-                    Slog.i(TAG, "Failed when private volume mounted " + vol, e);
-                }
             }
         }
 
@@ -3110,6 +3103,15 @@
 
         try {
             mVold.prepareUserStorage(volumeUuid, userId, serialNumber, flags);
+            // After preparing user storage, we should check if we should mount data mirror again,
+            // and we do it for user 0 only as we only need to do once for all users.
+            if (volumeUuid != null) {
+                final StorageManager storage = mContext.getSystemService(StorageManager.class);
+                VolumeInfo info = storage.findVolumeByUuid(volumeUuid);
+                if (info != null && userId == 0 && info.type == VolumeInfo.TYPE_PRIVATE) {
+                    mInstaller.tryMountDataMirror(volumeUuid);
+                }
+            }
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -3271,7 +3273,7 @@
                         + " does not match calling user id " + userId);
             }
             try {
-                mVold.setupAppDir(appPath, matcher.group(1), callingUid);
+                mVold.setupAppDir(appPath, callingUid);
             } catch (RemoteException e) {
                 throw new IllegalStateException("Failed to prepare " + appPath + ": " + e);
             }
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 0e5a6bb..f85fc28 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -61,6 +61,7 @@
 import android.telephony.CellSignalStrengthWcdma;
 import android.telephony.DataFailCause;
 import android.telephony.DisconnectCause;
+import android.telephony.DisplayInfo;
 import android.telephony.LocationAccessPolicy;
 import android.telephony.PhoneCapability;
 import android.telephony.PhoneStateListener;
@@ -205,6 +206,8 @@
 
     private boolean[] mUserMobileDataState;
 
+    private DisplayInfo[] mDisplayInfos;
+
     private SignalStrength[] mSignalStrength;
 
     private boolean[] mMessageWaiting;
@@ -284,7 +287,8 @@
     static final int ENFORCE_PHONE_STATE_PERMISSION_MASK =
                 PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR
                         | PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR
-                        | PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST;
+                        | PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST
+                        | PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED;
 
     static final int ENFORCE_PRECISE_PHONE_STATE_PERMISSION_MASK =
                 PhoneStateListener.LISTEN_PRECISE_CALL_STATE
@@ -443,6 +447,7 @@
         mCallAttributes = copyOf(mCallAttributes, mNumPhones);
         mOutgoingCallEmergencyNumber = copyOf(mOutgoingCallEmergencyNumber, mNumPhones);
         mOutgoingSmsEmergencyNumber = copyOf(mOutgoingSmsEmergencyNumber, mNumPhones);
+        mDisplayInfos = copyOf(mDisplayInfos, mNumPhones);
 
         // ds -> ss switch.
         if (mNumPhones < oldNumPhones) {
@@ -482,6 +487,7 @@
             mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
             mPreciseDataConnectionStates.add(new HashMap<Integer, PreciseDataConnectionState>());
             mBarringInfo.add(i, new BarringInfo());
+            mDisplayInfos[i] = null;
         }
     }
 
@@ -540,6 +546,7 @@
         mOutgoingCallEmergencyNumber = new EmergencyNumber[numPhones];
         mOutgoingSmsEmergencyNumber = new EmergencyNumber[numPhones];
         mBarringInfo = new ArrayList<>();
+        mDisplayInfos = new DisplayInfo[numPhones];
         for (int i = 0; i < numPhones; i++) {
             mCallState[i] =  TelephonyManager.CALL_STATE_IDLE;
             mDataActivity[i] = TelephonyManager.DATA_ACTIVITY_NONE;
@@ -568,6 +575,7 @@
             mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
             mPreciseDataConnectionStates.add(new HashMap<Integer, PreciseDataConnectionState>());
             mBarringInfo.add(i, new BarringInfo());
+            mDisplayInfos[i] = null;
         }
 
         mAppOps = mContext.getSystemService(AppOpsManager.class);
@@ -978,6 +986,15 @@
                             remove(r.binder);
                         }
                     }
+                    if ((events & PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED) != 0) {
+                        try {
+                            if (mDisplayInfos[phoneId] != null) {
+                                r.callback.onDisplayInfoChanged(mDisplayInfos[phoneId]);
+                            }
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
+                    }
                     if ((events & PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST) != 0) {
                         try {
                             r.callback.onEmergencyNumberListChanged(mEmergencyNumberList);
@@ -1501,6 +1518,45 @@
         }
     }
 
+    /**
+     * Notify display network info changed.
+     *
+     * @param phoneId Phone id
+     * @param subId Subscription id
+     * @param displayInfo Display network info
+     *
+     * @see PhoneStateListener#onDisplayInfoChanged(DisplayInfo)
+     */
+    public void notifyDisplayInfoChanged(int phoneId, int subId,
+                                         @NonNull DisplayInfo displayInfo) {
+        if (!checkNotifyPermission("notifyDisplayInfoChanged()")) {
+            return;
+        }
+        if (VDBG) {
+            log("notifyDisplayInfoChanged: PhoneId=" + phoneId
+                    + " subId=" + subId + " displayInfo=" + displayInfo);
+        }
+        synchronized (mRecords) {
+            if (validatePhoneId(phoneId)) {
+                if (mDisplayInfos[phoneId] != null) {
+                    mDisplayInfos[phoneId] = displayInfo;
+                    for (Record r : mRecords) {
+                        if (r.matchPhoneStateListenerEvent(
+                                PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED)
+                                && idMatch(r.subId, subId, phoneId)) {
+                            try {
+                                r.callback.onDisplayInfoChanged(displayInfo);
+                            } catch (RemoteException ex) {
+                                mRemoveList.add(r.binder);
+                            }
+                        }
+                    }
+                }
+            }
+            handleRemoveListLocked();
+        }
+    }
+
     public void notifyCallForwardingChanged(boolean cfi) {
         notifyCallForwardingChangedForSubscriber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, cfi);
     }
@@ -2730,6 +2786,20 @@
             }
         }
 
+        if ((events & PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED) != 0) {
+            try {
+                if (VDBG) {
+                    log("checkPossibleMissNotify: onDisplayInfoChanged phoneId="
+                            + phoneId + " dpi=" + mDisplayInfos[phoneId]);
+                }
+                if (mDisplayInfos[phoneId] != null) {
+                    r.callback.onDisplayInfoChanged(mDisplayInfos[phoneId]);
+                }
+            } catch (RemoteException ex) {
+                mRemoveList.add(r.binder);
+            }
+        }
+
         if ((events & PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR) != 0) {
             try {
                 if (VDBG) {
diff --git a/services/core/java/com/android/server/UserspaceRebootLogger.java b/services/core/java/com/android/server/UserspaceRebootLogger.java
new file mode 100644
index 0000000..74f113f
--- /dev/null
+++ b/services/core/java/com/android/server/UserspaceRebootLogger.java
@@ -0,0 +1,136 @@
+/*
+ * 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;
+
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_SHUTDOWN_SEQUENCE_ABORTED;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERDATA_REMOUNT;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERSPACE_REBOOT_WATCHDOG_TRIGGERED;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__OUTCOME_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__SUCCESS;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__LOCKED;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__UNLOCKED;
+
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.util.Slog;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Utility class to help abstract logging {@code UserspaceRebootReported} atom.
+ */
+public final class UserspaceRebootLogger {
+
+    private static final String TAG = "UserspaceRebootLogger";
+
+    private static final String USERSPACE_REBOOT_SHOULD_LOG_PROPERTY =
+            "persist.sys.userspace_reboot.log.should_log";
+    private static final String USERSPACE_REBOOT_LAST_STARTED_PROPERTY =
+            "sys.userspace_reboot.log.last_started";
+    private static final String USERSPACE_REBOOT_LAST_FINISHED_PROPERTY =
+            "sys.userspace_reboot.log.last_finished";
+    private static final String BOOT_REASON_PROPERTY = "sys.boot.reason";
+
+    private UserspaceRebootLogger() {}
+
+    /**
+     * Modifies internal state to note that {@code UserspaceRebootReported} atom needs to be
+     * logged on the next successful boot.
+     */
+    public static void noteUserspaceRebootWasRequested() {
+        SystemProperties.set(USERSPACE_REBOOT_SHOULD_LOG_PROPERTY, "1");
+        SystemProperties.set(USERSPACE_REBOOT_LAST_STARTED_PROPERTY,
+                String.valueOf(SystemClock.elapsedRealtime()));
+    }
+
+    /**
+     * Updates internal state on boot after successful userspace reboot.
+     *
+     * <p>Should be called right before framework sets {@code sys.boot_completed} property.
+     */
+    public static void noteUserspaceRebootSuccess() {
+        SystemProperties.set(USERSPACE_REBOOT_LAST_FINISHED_PROPERTY,
+                String.valueOf(SystemClock.elapsedRealtime()));
+    }
+
+    /**
+     * Returns {@code true} if {@code UserspaceRebootReported} atom should be logged.
+     */
+    public static boolean shouldLogUserspaceRebootEvent() {
+        return SystemProperties.getBoolean(USERSPACE_REBOOT_SHOULD_LOG_PROPERTY, false);
+    }
+
+    /**
+     * Asynchronously logs {@code UserspaceRebootReported} on the given {@code executor}.
+     *
+     * <p>Should be called in the end of {@link
+     * com.android.server.am.ActivityManagerService#finishBooting()} method, after framework have
+     * tried to proactivelly unlock storage of the primary user.
+     */
+    public static void logEventAsync(boolean userUnlocked, Executor executor) {
+        final int outcome = computeOutcome();
+        final long durationMillis;
+        if (outcome == USERSPACE_REBOOT_REPORTED__OUTCOME__SUCCESS) {
+            durationMillis = SystemProperties.getLong(USERSPACE_REBOOT_LAST_FINISHED_PROPERTY, 0)
+                    - SystemProperties.getLong(USERSPACE_REBOOT_LAST_STARTED_PROPERTY, 0);
+        } else {
+            durationMillis = 0;
+        }
+        final int encryptionState =
+                userUnlocked
+                    ? USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__UNLOCKED
+                    : USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__LOCKED;
+        executor.execute(
+                () -> {
+                    Slog.i(TAG, "Logging UserspaceRebootReported atom: { outcome: " + outcome
+                            + " durationMillis: " + durationMillis + " encryptionState: "
+                            + encryptionState + " }");
+                    FrameworkStatsLog.write(FrameworkStatsLog.USERSPACE_REBOOT_REPORTED, outcome,
+                            durationMillis, encryptionState);
+                    SystemProperties.set(USERSPACE_REBOOT_SHOULD_LOG_PROPERTY, "");
+                });
+    }
+
+    private static int computeOutcome() {
+        if (SystemProperties.getLong(USERSPACE_REBOOT_LAST_STARTED_PROPERTY, -1) != -1) {
+            return USERSPACE_REBOOT_REPORTED__OUTCOME__SUCCESS;
+        }
+        String reason = SystemProperties.get(BOOT_REASON_PROPERTY, "");
+        if (reason.startsWith("reboot,")) {
+            reason = reason.substring("reboot".length());
+        }
+        switch (reason) {
+            case "userspace_failed,watchdog_fork":
+                // Since fork happens before shutdown sequence, attribute it to
+                // USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_SHUTDOWN_SEQUENCE_ABORTED.
+            case "userspace_failed,shutdown_aborted":
+                return USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_SHUTDOWN_SEQUENCE_ABORTED;
+            case "userspace_failed,init_user0_failed":
+                // init_user0 will fail if userdata wasn't remounted correctly, attribute to
+                // USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERDATA_REMOUNT.
+            case "mount_userdata_failed":
+                return USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERDATA_REMOUNT;
+            case "userspace_failed,watchdog_triggered":
+                return
+                    USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERSPACE_REBOOT_WATCHDOG_TRIGGERED;
+            default:
+                return USERSPACE_REBOOT_REPORTED__OUTCOME__OUTCOME_UNKNOWN;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 0852458..ad550c5 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -278,6 +278,7 @@
 import android.provider.DeviceConfig.Properties;
 import android.provider.Settings;
 import android.server.ServerProtoEnums;
+import android.sysprop.InitProperties;
 import android.sysprop.VoldProperties;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
@@ -347,6 +348,7 @@
 import com.android.server.SystemService;
 import com.android.server.SystemServiceManager;
 import com.android.server.ThreadPriorityBooster;
+import com.android.server.UserspaceRebootLogger;
 import com.android.server.Watchdog;
 import com.android.server.am.ActivityManagerServiceDumpProcessesProto.UidObserverRegistrationProto;
 import com.android.server.appop.AppOpsService;
@@ -2282,6 +2284,20 @@
         }
     }
 
+    private void maybeLogUserspaceRebootEvent() {
+        if (!UserspaceRebootLogger.shouldLogUserspaceRebootEvent()) {
+            return;
+        }
+        final int userId = mUserController.getCurrentUserId();
+        if (userId != UserHandle.USER_SYSTEM) {
+            // Only log for user0.
+            return;
+        }
+        // TODO(b/148767783): should we check all profiles under user0?
+        UserspaceRebootLogger.logEventAsync(StorageManager.isUserKeyUnlocked(userId),
+                BackgroundThread.getExecutor());
+    }
+
     /**
      * Encapsulates global settings related to hidden API enforcement behaviour, including tracking
      * the latest value via a content observer.
@@ -5361,6 +5377,12 @@
             // Start looking for apps that are abusing wake locks.
             Message nmsg = mHandler.obtainMessage(CHECK_EXCESSIVE_POWER_USE_MSG);
             mHandler.sendMessageDelayed(nmsg, mConstants.POWER_CHECK_INTERVAL);
+            // Check if we are performing userspace reboot before setting sys.boot_completed to
+            // avoid race with init reseting sys.init.userspace_reboot.in_progress once sys
+            // .boot_completed is 1.
+            if (InitProperties.userspace_reboot_in_progress().orElse(false)) {
+                UserspaceRebootLogger.noteUserspaceRebootSuccess();
+            }
             // Tell anyone interested that we are done booting!
             SystemProperties.set("sys.boot_completed", "1");
 
@@ -5381,6 +5403,7 @@
                             }
                         }
                     });
+            maybeLogUserspaceRebootEvent();
             mUserController.scheduleStartProfiles();
         }
         // UART is on if init's console service is running, send a warning notification.
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 06561f5..3ffe1be 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -56,7 +56,6 @@
 import static android.content.Intent.ACTION_PACKAGE_REMOVED;
 import static android.content.Intent.EXTRA_REPLACING;
 import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
-import static android.os.Process.STATSD_UID;
 
 import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
 
@@ -411,9 +410,9 @@
                     Slog.e(TAG, "Bad app ops settings", e);
                 }
                 TOP_STATE_SETTLE_TIME = mParser.getDurationMillis(
-                        KEY_TOP_STATE_SETTLE_TIME, 30 * 1000L);
+                        KEY_TOP_STATE_SETTLE_TIME, 5 * 1000L);
                 FG_SERVICE_STATE_SETTLE_TIME = mParser.getDurationMillis(
-                        KEY_FG_SERVICE_STATE_SETTLE_TIME, 10 * 1000L);
+                        KEY_FG_SERVICE_STATE_SETTLE_TIME, 5 * 1000L);
                 BG_STATE_SETTLE_TIME = mParser.getDurationMillis(
                         KEY_BG_STATE_SETTLE_TIME, 1 * 1000L);
             }
@@ -1890,9 +1889,9 @@
 
         ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
         boolean isCallerInstrumented = ami.isUidCurrentlyInstrumented(Binder.getCallingUid());
-        boolean isCallerStatsCollector = Binder.getCallingUid() == STATSD_UID;
+        boolean isCallerSystem = Binder.getCallingPid() == Process.myPid();
 
-        if (!isCallerStatsCollector && !isCallerInstrumented) {
+        if (!isCallerSystem && !isCallerInstrumented) {
             mHandler.post(() -> callback.sendResult(new Bundle()));
             return;
         }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 97a8b87..67d7530 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -128,6 +128,7 @@
 import android.util.MathUtils;
 import android.util.PrintWriterPrinter;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.view.KeyEvent;
 import android.view.accessibility.AccessibilityManager;
@@ -247,6 +248,7 @@
     // AudioHandler messages
     private static final int MSG_SET_DEVICE_VOLUME = 0;
     private static final int MSG_PERSIST_VOLUME = 1;
+    private static final int MSG_PERSIST_VOLUME_GROUP = 2;
     private static final int MSG_PERSIST_RINGER_MODE = 3;
     private static final int MSG_AUDIO_SERVER_DIED = 4;
     private static final int MSG_PLAY_SOUND_EFFECT = 5;
@@ -780,6 +782,10 @@
         mSettingsObserver = new SettingsObserver();
         createStreamStates();
 
+        // must be called after createStreamStates() as it uses MUSIC volume as default if no
+        // persistent data
+        initVolumeGroupStates();
+
         // mSafeUsbMediaVolumeIndex must be initialized after createStreamStates() because it
         // relies on audio policy having correct ranges for volume indexes.
         mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex();
@@ -1018,6 +1024,9 @@
             streamState.applyAllVolumes();
         }
 
+        // Restore audio volume groups
+        restoreVolumeGroups();
+
         // Restore mono mode
         updateMasterMono(mContentResolver);
 
@@ -2288,20 +2297,20 @@
                                             String callingPackage) {
         enforceModifyAudioRoutingPermission();
         Objects.requireNonNull(attr, "attr must not be null");
-        // @todo not hold the caller context, post message
-        int stream = AudioProductStrategy.getLegacyStreamTypeForStrategyWithAudioAttributes(attr);
-        final int device = getDeviceForStream(stream);
-
-        int oldIndex = AudioSystem.getVolumeIndexForAttributes(attr, device);
-
-        AudioSystem.setVolumeIndexForAttributes(attr, index, device);
-
         final int volumeGroup = getVolumeGroupIdForAttributes(attr);
-        final AudioVolumeGroup avg = getAudioVolumeGroupById(volumeGroup);
-        if (avg == null) {
+        if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) {
+            Log.e(TAG, ": no volume group found for attributes " + attr.toString());
             return;
         }
-        for (final int groupedStream : avg.getLegacyStreamTypes()) {
+        final VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup);
+
+        sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_GROUP_VOL, attr, vgs.name(),
+                index/*val1*/, flags/*val2*/, callingPackage));
+
+        vgs.setVolumeIndex(index, flags);
+
+        // For legacy reason, propagate to all streams associated to this volume group
+        for (final int groupedStream : vgs.getLegacyStreamTypes()) {
             setStreamVolume(groupedStream, index, flags, callingPackage, callingPackage,
                             Binder.getCallingUid());
         }
@@ -2323,10 +2332,12 @@
     public int getVolumeIndexForAttributes(@NonNull AudioAttributes attr) {
         enforceModifyAudioRoutingPermission();
         Objects.requireNonNull(attr, "attr must not be null");
-        int stream = AudioProductStrategy.getLegacyStreamTypeForStrategyWithAudioAttributes(attr);
-        final int device = getDeviceForStream(stream);
-
-        return AudioSystem.getVolumeIndexForAttributes(attr, device);
+        final int volumeGroup = getVolumeGroupIdForAttributes(attr);
+        if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) {
+            throw new IllegalArgumentException("No volume group for attributes " + attr);
+        }
+        final VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup);
+        return vgs.getVolumeIndex();
     }
 
     /** @see AudioManager#getMaxVolumeIndexForAttributes(attr) */
@@ -3754,6 +3765,8 @@
                 enforceSafeMediaVolume(TAG);
             }
         }
+
+        readVolumeGroupsSettings();
     }
 
     /** @see AudioManager#setSpeakerphoneOn(boolean) */
@@ -4654,6 +4667,310 @@
     ///////////////////////////////////////////////////////////////////////////
     // Inner classes
     ///////////////////////////////////////////////////////////////////////////
+    /**
+     * Key is the AudioManager VolumeGroupId
+     * Value is the VolumeGroupState
+     */
+    private static final SparseArray<VolumeGroupState> sVolumeGroupStates = new SparseArray<>();
+
+    private void initVolumeGroupStates() {
+        for (final AudioVolumeGroup avg : getAudioVolumeGroups()) {
+            try {
+                // if no valid attributes, this volume group is not controllable, throw exception
+                ensureValidAttributes(avg);
+            } catch (IllegalArgumentException e) {
+                // Volume Groups without attributes are not controllable through set/get volume
+                // using attributes. Do not append them.
+                Log.d(TAG, "volume group " + avg.name() + " for internal policy needs");
+                continue;
+            }
+            sVolumeGroupStates.append(avg.getId(), new VolumeGroupState(avg));
+        }
+        for (int i = 0; i < sVolumeGroupStates.size(); i++) {
+            final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
+            vgs.applyAllVolumes();
+        }
+    }
+
+    private void ensureValidAttributes(AudioVolumeGroup avg) {
+        boolean hasAtLeastOneValidAudioAttributes = avg.getAudioAttributes().stream()
+                .anyMatch(aa -> !aa.equals(AudioProductStrategy.sDefaultAttributes));
+        if (!hasAtLeastOneValidAudioAttributes) {
+            throw new IllegalArgumentException("Volume Group " + avg.name()
+                    + " has no valid audio attributes");
+        }
+    }
+
+    private void readVolumeGroupsSettings() {
+        Log.v(TAG, "readVolumeGroupsSettings");
+        for (int i = 0; i < sVolumeGroupStates.size(); i++) {
+            final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
+            vgs.readSettings();
+            vgs.applyAllVolumes();
+        }
+    }
+
+    // Called upon crash of AudioServer
+    private void restoreVolumeGroups() {
+        Log.v(TAG, "restoreVolumeGroups");
+        for (int i = 0; i < sVolumeGroupStates.size(); i++) {
+            final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
+            vgs.applyAllVolumes();
+        }
+    }
+
+    private void dumpVolumeGroups(PrintWriter pw) {
+        pw.println("\nVolume Groups (device: index)");
+        for (int i = 0; i < sVolumeGroupStates.size(); i++) {
+            final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
+            vgs.dump(pw);
+            pw.println("");
+        }
+    }
+
+    // NOTE: Locking order for synchronized objects related to volume management:
+    //  1     mSettingsLock
+    //  2       VolumeGroupState.class
+    private class VolumeGroupState {
+        private final AudioVolumeGroup mAudioVolumeGroup;
+        private final SparseIntArray mIndexMap = new SparseIntArray(8);
+        private int mIndexMin;
+        private int mIndexMax;
+        private int mLegacyStreamType = AudioSystem.STREAM_DEFAULT;
+        private int mPublicStreamType = AudioSystem.STREAM_MUSIC;
+        private AudioAttributes mAudioAttributes = AudioProductStrategy.sDefaultAttributes;
+
+        // No API in AudioSystem to get a device from strategy or from attributes.
+        // Need a valid public stream type to use current API getDeviceForStream
+        private int getDeviceForVolume() {
+            return getDeviceForStream(mPublicStreamType);
+        }
+
+        private VolumeGroupState(AudioVolumeGroup avg) {
+            mAudioVolumeGroup = avg;
+            Log.v(TAG, "VolumeGroupState for " + avg.toString());
+            for (final AudioAttributes aa : avg.getAudioAttributes()) {
+                if (!aa.equals(AudioProductStrategy.sDefaultAttributes)) {
+                    mAudioAttributes = aa;
+                    break;
+                }
+            }
+            final int[] streamTypes = mAudioVolumeGroup.getLegacyStreamTypes();
+            if (streamTypes.length != 0) {
+                // Uses already initialized MIN / MAX if a stream type is attached to group
+                mLegacyStreamType = streamTypes[0];
+                for (final int streamType : streamTypes) {
+                    if (streamType != AudioSystem.STREAM_DEFAULT
+                            && streamType < AudioSystem.getNumStreamTypes()) {
+                        mPublicStreamType = streamType;
+                        break;
+                    }
+                }
+                mIndexMin = MIN_STREAM_VOLUME[mPublicStreamType];
+                mIndexMax = MAX_STREAM_VOLUME[mPublicStreamType];
+            } else if (!avg.getAudioAttributes().isEmpty()) {
+                mIndexMin = AudioSystem.getMinVolumeIndexForAttributes(mAudioAttributes);
+                mIndexMax = AudioSystem.getMaxVolumeIndexForAttributes(mAudioAttributes);
+            } else {
+                Log.e(TAG, "volume group: " + mAudioVolumeGroup.name()
+                        + " has neither valid attributes nor valid stream types assigned");
+                return;
+            }
+            // Load volume indexes from data base
+            readSettings();
+        }
+
+        public @NonNull int[] getLegacyStreamTypes() {
+            return mAudioVolumeGroup.getLegacyStreamTypes();
+        }
+
+        public String name() {
+            return mAudioVolumeGroup.name();
+        }
+
+        public int getVolumeIndex() {
+            return getIndex(getDeviceForVolume());
+        }
+
+        public void setVolumeIndex(int index, int flags) {
+            if (mUseFixedVolume) {
+                return;
+            }
+            setVolumeIndex(index, getDeviceForVolume(), flags);
+        }
+
+        private void setVolumeIndex(int index, int device, int flags) {
+            // Set the volume index
+            setVolumeIndexInt(index, device, flags);
+
+            // Update local cache
+            mIndexMap.put(device, index);
+
+            // update data base - post a persist volume group msg
+            sendMsg(mAudioHandler,
+                    MSG_PERSIST_VOLUME_GROUP,
+                    SENDMSG_QUEUE,
+                    device,
+                    0,
+                    this,
+                    PERSIST_DELAY);
+        }
+
+        private void setVolumeIndexInt(int index, int device, int flags) {
+            // Set the volume index
+            AudioSystem.setVolumeIndexForAttributes(mAudioAttributes, index, device);
+        }
+
+        public int getIndex(int device) {
+            synchronized (VolumeGroupState.class) {
+                int index = mIndexMap.get(device, -1);
+                // there is always an entry for AudioSystem.DEVICE_OUT_DEFAULT
+                return (index != -1) ? index : mIndexMap.get(AudioSystem.DEVICE_OUT_DEFAULT);
+            }
+        }
+
+        public boolean hasIndexForDevice(int device) {
+            synchronized (VolumeGroupState.class) {
+                return (mIndexMap.get(device, -1) != -1);
+            }
+        }
+
+        public int getMaxIndex() {
+            return mIndexMax;
+        }
+
+        public int getMinIndex() {
+            return mIndexMin;
+        }
+
+        public void applyAllVolumes() {
+            synchronized (VolumeGroupState.class) {
+                if (mLegacyStreamType != AudioSystem.STREAM_DEFAULT) {
+                    // No-op to avoid regression with stream based volume management
+                    return;
+                }
+                // apply device specific volumes first
+                int index;
+                for (int i = 0; i < mIndexMap.size(); i++) {
+                    final int device = mIndexMap.keyAt(i);
+                    if (device != AudioSystem.DEVICE_OUT_DEFAULT) {
+                        index = mIndexMap.valueAt(i);
+                        Log.v(TAG, "applyAllVolumes: restore index " + index + " for group "
+                                + mAudioVolumeGroup.name() + " and device "
+                                + AudioSystem.getOutputDeviceName(device));
+                        setVolumeIndexInt(index, device, 0 /*flags*/);
+                    }
+                }
+                // apply default volume last: by convention , default device volume will be used
+                index = getIndex(AudioSystem.DEVICE_OUT_DEFAULT);
+                Log.v(TAG, "applyAllVolumes: restore default device index " + index + " for group "
+                        + mAudioVolumeGroup.name());
+                setVolumeIndexInt(index, AudioSystem.DEVICE_OUT_DEFAULT, 0 /*flags*/);
+            }
+        }
+
+        private void persistVolumeGroup(int device) {
+            if (mUseFixedVolume) {
+                return;
+            }
+            Log.v(TAG, "persistVolumeGroup: storing index " + getIndex(device) + " for group "
+                    + mAudioVolumeGroup.name() + " and device "
+                    + AudioSystem.getOutputDeviceName(device));
+            boolean success = Settings.System.putIntForUser(mContentResolver,
+                    getSettingNameForDevice(device),
+                    getIndex(device),
+                    UserHandle.USER_CURRENT);
+            if (!success) {
+                Log.e(TAG, "persistVolumeGroup failed for group " +  mAudioVolumeGroup.name());
+            }
+        }
+
+        public void readSettings() {
+            synchronized (VolumeGroupState.class) {
+                // First clear previously loaded (previous user?) settings
+                mIndexMap.clear();
+                // force maximum volume on all streams if fixed volume property is set
+                if (mUseFixedVolume) {
+                    mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, mIndexMax);
+                    return;
+                }
+                for (int device : AudioSystem.DEVICE_OUT_ALL_SET) {
+                    // retrieve current volume for device
+                    // if no volume stored for current volume group and device, use default volume
+                    // if default device, continue otherwise
+                    int defaultIndex = (device == AudioSystem.DEVICE_OUT_DEFAULT)
+                            ? AudioSystem.DEFAULT_STREAM_VOLUME[mPublicStreamType] : -1;
+                    int index;
+                    String name = getSettingNameForDevice(device);
+                    index = Settings.System.getIntForUser(
+                            mContentResolver, name, defaultIndex, UserHandle.USER_CURRENT);
+                    if (index == -1) {
+                        Log.e(TAG, "readSettings: No index stored for group "
+                                + mAudioVolumeGroup.name() + ", device " + name);
+                        continue;
+                    }
+                    Log.v(TAG, "readSettings: found stored index " + getValidIndex(index)
+                             + " for group " + mAudioVolumeGroup.name() + ", device: " + name);
+                    mIndexMap.put(device, getValidIndex(index));
+                }
+            }
+        }
+
+        private int getValidIndex(int index) {
+            if (index < mIndexMin) {
+                return mIndexMin;
+            } else if (mUseFixedVolume || index > mIndexMax) {
+                return mIndexMax;
+            }
+            return index;
+        }
+
+        public @NonNull String getSettingNameForDevice(int device) {
+            final String suffix = AudioSystem.getOutputDeviceName(device);
+            if (suffix.isEmpty()) {
+                return mAudioVolumeGroup.name();
+            }
+            return mAudioVolumeGroup.name() + "_" + AudioSystem.getOutputDeviceName(device);
+        }
+
+        private void dump(PrintWriter pw) {
+            pw.println("- VOLUME GROUP " + mAudioVolumeGroup.name() + ":");
+            pw.print("   Min: ");
+            pw.println(mIndexMin);
+            pw.print("   Max: ");
+            pw.println(mIndexMax);
+            pw.print("   Current: ");
+            for (int i = 0; i < mIndexMap.size(); i++) {
+                if (i > 0) {
+                    pw.print(", ");
+                }
+                final int device = mIndexMap.keyAt(i);
+                pw.print(Integer.toHexString(device));
+                final String deviceName = device == AudioSystem.DEVICE_OUT_DEFAULT ? "default"
+                        : AudioSystem.getOutputDeviceName(device);
+                if (!deviceName.isEmpty()) {
+                    pw.print(" (");
+                    pw.print(deviceName);
+                    pw.print(")");
+                }
+                pw.print(": ");
+                pw.print(mIndexMap.valueAt(i));
+            }
+            pw.println();
+            pw.print("   Devices: ");
+            int n = 0;
+            final int devices = getDeviceForVolume();
+            for (int device : AudioSystem.DEVICE_OUT_ALL_SET) {
+                if ((devices & device) == device) {
+                    if (n++ > 0) {
+                        pw.print(", ");
+                    }
+                    pw.print(AudioSystem.getOutputDeviceName(device));
+                }
+            }
+        }
+    }
+
 
     // NOTE: Locking order for synchronized objects related to volume or ringer mode management:
     //  1 mScoclient OR mSafeMediaVolumeState
@@ -5298,6 +5615,11 @@
                     persistVolume((VolumeStreamState) msg.obj, msg.arg1);
                     break;
 
+                case MSG_PERSIST_VOLUME_GROUP:
+                    final VolumeGroupState vgs = (VolumeGroupState) msg.obj;
+                    vgs.persistVolumeGroup(msg.arg1);
+                    break;
+
                 case MSG_PERSIST_RINGER_MODE:
                     // note that the value persisted is the current ringer mode, not the
                     // value of ringer mode as of the time the request was made to persist
@@ -6395,6 +6717,7 @@
         }
         mMediaFocusControl.dump(pw);
         dumpStreamStates(pw);
+        dumpVolumeGroups(pw);
         dumpRingerMode(pw);
         pw.println("\nAudio routes:");
         pw.print("  mMainType=0x"); pw.println(Integer.toHexString(
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index fcd8701..add620e 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -16,6 +16,7 @@
 
 package com.android.server.audio;
 
+import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.media.AudioSystem;
 
@@ -97,12 +98,15 @@
         static final int VOL_ADJUST_VOL_UID = 5;
         static final int VOL_VOICE_ACTIVITY_HEARING_AID = 6;
         static final int VOL_MODE_CHANGE_HEARING_AID = 7;
+        static final int VOL_SET_GROUP_VOL = 8;
 
         final int mOp;
         final int mStream;
         final int mVal1;
         final int mVal2;
         final String mCaller;
+        final String mGroupName;
+        final AudioAttributes mAudioAttributes;
 
         /** used for VOL_ADJUST_VOL_UID,
          *           VOL_ADJUST_SUGG_VOL,
@@ -114,6 +118,8 @@
             mVal1 = val1;
             mVal2 = val2;
             mCaller = caller;
+            mGroupName = null;
+            mAudioAttributes = null;
         }
 
         /** used for VOL_SET_HEARING_AID_VOL*/
@@ -124,6 +130,8 @@
             // unused
             mStream = -1;
             mCaller = null;
+            mGroupName = null;
+            mAudioAttributes = null;
         }
 
         /** used for VOL_SET_AVRCP_VOL */
@@ -134,6 +142,8 @@
             mVal2 = 0;
             mStream = -1;
             mCaller = null;
+            mGroupName = null;
+            mAudioAttributes = null;
         }
 
         /** used for VOL_VOICE_ACTIVITY_HEARING_AID */
@@ -144,6 +154,8 @@
             mVal2 = voiceActive ? 1 : 0;
             // unused
             mCaller = null;
+            mGroupName = null;
+            mAudioAttributes = null;
         }
 
         /** used for VOL_MODE_CHANGE_HEARING_AID */
@@ -154,6 +166,19 @@
             mVal2 = mode;
             // unused
             mCaller = null;
+            mGroupName = null;
+            mAudioAttributes = null;
+        }
+
+        /** used for VOL_SET_GROUP_VOL */
+        VolumeEvent(int op, AudioAttributes aa, String group, int index, int flags, String caller) {
+            mOp = op;
+            mStream = -1;
+            mVal1 = index;
+            mVal2 = flags;
+            mCaller = caller;
+            mGroupName = group;
+            mAudioAttributes = aa;
         }
 
         @Override
@@ -208,6 +233,14 @@
                             .append(") causes setting HEARING_AID volume to idx:").append(mVal1)
                             .append(" stream:").append(AudioSystem.streamToString(mStream))
                             .toString();
+                case VOL_SET_GROUP_VOL:
+                    return new StringBuilder("setVolumeIndexForAttributes(attr:")
+                            .append(mAudioAttributes.toString())
+                            .append(" group: ").append(mGroupName)
+                            .append(" index:").append(mVal1)
+                            .append(" flags:0x").append(Integer.toHexString(mVal2))
+                            .append(") from ").append(mCaller)
+                            .toString();
                 default: return new StringBuilder("FIXME invalid op:").append(mOp).toString();
             }
         }
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index df1b899..3f6e88d 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -782,6 +782,8 @@
             }
 
             mAuthenticators.add(new AuthenticatorWrapper(id, modality, strength, authenticator));
+
+            mBiometricStrengthController.updateStrengths();
         }
 
         @Override // Binder call
diff --git a/services/core/java/com/android/server/biometrics/BiometricStrengthController.java b/services/core/java/com/android/server/biometrics/BiometricStrengthController.java
index 4e16189..ca7ca5b 100644
--- a/services/core/java/com/android/server/biometrics/BiometricStrengthController.java
+++ b/services/core/java/com/android/server/biometrics/BiometricStrengthController.java
@@ -29,7 +29,7 @@
  * Class for maintaining and updating the strengths for biometric sensors. Strengths can only
  * be downgraded from the device's default, and never upgraded.
  */
-public class BiometricStrengthController implements DeviceConfig.OnPropertiesChangedListener {
+public class BiometricStrengthController {
     private static final String TAG = "BiometricStrengthController";
 
     private final BiometricService mService;
@@ -41,7 +41,7 @@
      * "id1:strength1,id2:strength2,id3:strength3"
      *
      * where strength is one of the values defined in
-     * {@link android.hardware.biometrics.Authenticators}
+     * {@link android.hardware.biometrics.BiometricManager.Authenticators}
      *
      * Both id and strength should be int, otherwise Exception will be thrown when parsing and the
      * downgrade will fail.
@@ -53,30 +53,28 @@
      */
     public static final String DEFAULT_BIOMETRIC_STRENGTHS = null;
 
-    BiometricStrengthController(@NonNull BiometricService service) {
-        mService = service;
-    }
-
-    void startListening() {
-        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_BIOMETRICS,
-                BackgroundThread.getExecutor(), this);
-        updateStrengths();
-    }
-
-    @Override
-    public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
+    private DeviceConfig.OnPropertiesChangedListener mDeviceConfigListener = properties -> {
         for (String name : properties.getKeyset()) {
             if (KEY_BIOMETRIC_STRENGTHS.equals(name)) {
                 updateStrengths();
             }
         }
+    };
+
+    public BiometricStrengthController(@NonNull BiometricService service) {
+        mService = service;
+    }
+
+    public void startListening() {
+        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_BIOMETRICS,
+                BackgroundThread.getExecutor(), mDeviceConfigListener);
     }
 
     /**
      * Updates the strengths of authenticators in BiometricService if a matching ID's configuration
      * has been changed.
      */
-    private void updateStrengths() {
+    public void updateStrengths() {
         final Map<Integer, Integer> idToStrength = getIdToStrengthMap();
         if (idToStrength == null) {
             return;
diff --git a/services/core/java/com/android/server/compat/CompatChange.java b/services/core/java/com/android/server/compat/CompatChange.java
index 2eec419..8687f35 100644
--- a/services/core/java/com/android/server/compat/CompatChange.java
+++ b/services/core/java/com/android/server/compat/CompatChange.java
@@ -17,6 +17,7 @@
 package com.android.server.compat;
 
 import android.annotation.Nullable;
+import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
 import android.content.pm.ApplicationInfo;
 
@@ -39,6 +40,13 @@
 public final class CompatChange extends CompatibilityChangeInfo {
 
     /**
+     * A change ID to be used only in the CTS test for this SystemApi
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = 1234) // Needs to be > test APK targetSdkVersion.
+    private static final long CTS_SYSTEM_API_CHANGEID = 149391281; // This is a bug id.
+
+    /**
      * Callback listener for when compat changes are updated for a package.
      * See {@link #registerListener(ChangeListener)} for more details.
      */
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index cb88c4e..1a68f1b 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -48,8 +48,12 @@
 import android.content.pm.UserInfo;
 import android.net.ConnectivityManager;
 import android.net.INetworkManagementEventObserver;
+import android.net.Ikev2VpnProfile;
 import android.net.IpPrefix;
 import android.net.IpSecManager;
+import android.net.IpSecManager.IpSecTunnelInterface;
+import android.net.IpSecManager.UdpEncapsulationSocket;
+import android.net.IpSecTransform;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.LocalSocket;
@@ -65,6 +69,12 @@
 import android.net.UidRange;
 import android.net.VpnManager;
 import android.net.VpnService;
+import android.net.ipsec.ike.ChildSessionCallback;
+import android.net.ipsec.ike.ChildSessionConfiguration;
+import android.net.ipsec.ike.ChildSessionParams;
+import android.net.ipsec.ike.IkeSession;
+import android.net.ipsec.ike.IkeSessionCallback;
+import android.net.ipsec.ike.IkeSessionParams;
 import android.os.Binder;
 import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
@@ -113,6 +123,7 @@
 import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -122,6 +133,9 @@
 import java.util.Set;
 import java.util.SortedSet;
 import java.util.TreeSet;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -176,14 +190,14 @@
 
     private final Context mContext;
     private final NetworkInfo mNetworkInfo;
-    private String mPackage;
+    @VisibleForTesting protected String mPackage;
     private int mOwnerUID;
     private boolean mIsPackageTargetingAtLeastQ;
     private String mInterface;
     private Connection mConnection;
 
     /** Tracks the runners for all VPN types managed by the platform (eg. LegacyVpn, PlatformVpn) */
-    private VpnRunner mVpnRunner;
+    @VisibleForTesting protected VpnRunner mVpnRunner;
 
     private PendingIntent mStatusIntent;
     private volatile boolean mEnableTeardown = true;
@@ -196,6 +210,7 @@
     @VisibleForTesting
     protected final NetworkCapabilities mNetworkCapabilities;
     private final SystemServices mSystemServices;
+    private final Ikev2SessionCreator mIkev2SessionCreator;
 
     /**
      * Whether to keep the connection active after rebooting, or upgrading or reinstalling. This
@@ -238,17 +253,20 @@
 
     public Vpn(Looper looper, Context context, INetworkManagementService netService,
             @UserIdInt int userHandle) {
-        this(looper, context, netService, userHandle, new SystemServices(context));
+        this(looper, context, netService, userHandle,
+                new SystemServices(context), new Ikev2SessionCreator());
     }
 
     @VisibleForTesting
     protected Vpn(Looper looper, Context context, INetworkManagementService netService,
-            int userHandle, SystemServices systemServices) {
+            int userHandle, SystemServices systemServices,
+            Ikev2SessionCreator ikev2SessionCreator) {
         mContext = context;
         mNetd = netService;
         mUserHandle = userHandle;
         mLooper = looper;
         mSystemServices = systemServices;
+        mIkev2SessionCreator = ikev2SessionCreator;
 
         mPackage = VpnConfig.LEGACY_VPN;
         mOwnerUID = getAppUid(mPackage, mUserHandle);
@@ -749,8 +767,9 @@
 
     private boolean isCurrentPreparedPackage(String packageName) {
         // We can't just check that packageName matches mPackage, because if the app was uninstalled
-        // and reinstalled it will no longer be prepared. Instead check the UID.
-        return getAppUid(packageName, mUserHandle) == mOwnerUID;
+        // and reinstalled it will no longer be prepared. Similarly if there is a shared UID, the
+        // calling package may not be the same as the prepared package. Check both UID and package.
+        return getAppUid(packageName, mUserHandle) == mOwnerUID && mPackage.equals(packageName);
     }
 
     /** Prepare the VPN for the given package. Does not perform permission checks. */
@@ -979,7 +998,11 @@
         }
         lp.setDomains(buffer.toString().trim());
 
-        // TODO: Stop setting the MTU in jniCreate and set it here.
+        if (mConfig.mtu > 0) {
+            lp.setMtu(mConfig.mtu);
+        }
+
+        // TODO: Stop setting the MTU in jniCreate
 
         return lp;
     }
@@ -2004,30 +2027,369 @@
         protected abstract void exit();
     }
 
-    private class IkeV2VpnRunner extends VpnRunner {
-        private static final String TAG = "IkeV2VpnRunner";
+    interface IkeV2VpnRunnerCallback {
+        void onDefaultNetworkChanged(@NonNull Network network);
 
-        private final IpSecManager mIpSecManager;
-        private final VpnProfile mProfile;
+        void onChildOpened(
+                @NonNull Network network, @NonNull ChildSessionConfiguration childConfig);
 
-        IkeV2VpnRunner(VpnProfile profile) {
+        void onChildTransformCreated(
+                @NonNull Network network, @NonNull IpSecTransform transform, int direction);
+
+        void onSessionLost(@NonNull Network network);
+    }
+
+    /**
+     * Internal class managing IKEv2/IPsec VPN connectivity
+     *
+     * <p>The IKEv2 VPN will listen to, and run based on the lifecycle of Android's default Network.
+     * As a new default is selected, old IKE sessions will be torn down, and a new one will be
+     * started.
+     *
+     * <p>This class uses locking minimally - the Vpn instance lock is only ever held when fields of
+     * the outer class are modified. As such, care must be taken to ensure that no calls are added
+     * that might modify the outer class' state without acquiring a lock.
+     *
+     * <p>The overall structure of the Ikev2VpnRunner is as follows:
+     *
+     * <ol>
+     *   <li>Upon startup, a NetworkRequest is registered with ConnectivityManager. This is called
+     *       any time a new default network is selected
+     *   <li>When a new default is connected, an IKE session is started on that Network. If there
+     *       were any existing IKE sessions on other Networks, they are torn down before starting
+     *       the new IKE session
+     *   <li>Upon establishment, the onChildTransformCreated() callback is called twice, one for
+     *       each direction, and finally onChildOpened() is called
+     *   <li>Upon the onChildOpened() call, the VPN is fully set up.
+     *   <li>Subsequent Network changes result in new onDefaultNetworkChanged() callbacks. See (2).
+     * </ol>
+     */
+    class IkeV2VpnRunner extends VpnRunner implements IkeV2VpnRunnerCallback {
+        @NonNull private static final String TAG = "IkeV2VpnRunner";
+
+        @NonNull private final IpSecManager mIpSecManager;
+        @NonNull private final Ikev2VpnProfile mProfile;
+        @NonNull private final ConnectivityManager.NetworkCallback mNetworkCallback;
+
+        /**
+         * Executor upon which ALL callbacks must be run.
+         *
+         * <p>This executor MUST be a single threaded executor, in order to ensure the consistency
+         * of the mutable Ikev2VpnRunner fields. The Ikev2VpnRunner is built mostly lock-free by
+         * virtue of everything being serialized on this executor.
+         */
+        @NonNull private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
+
+        /** Signal to ensure shutdown is honored even if a new Network is connected. */
+        private boolean mIsRunning = true;
+
+        @Nullable private UdpEncapsulationSocket mEncapSocket;
+        @Nullable private IpSecTunnelInterface mTunnelIface;
+        @Nullable private IkeSession mSession;
+        @Nullable private Network mActiveNetwork;
+
+        IkeV2VpnRunner(@NonNull Ikev2VpnProfile profile) {
             super(TAG);
             mProfile = profile;
-
-            // TODO: move this to startVpnRunnerPrivileged()
-            mConfig = new VpnConfig();
-            mIpSecManager = mContext.getSystemService(IpSecManager.class);
+            mIpSecManager = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE);
+            mNetworkCallback = new VpnIkev2Utils.Ikev2VpnNetworkCallback(TAG, this);
         }
 
         @Override
         public void run() {
-            // TODO: Build IKE config, start IKE session
+            // Explicitly use only the network that ConnectivityService thinks is the "best." In
+            // other words, only ever use the currently selected default network. This does mean
+            // that in both onLost() and onConnected(), any old sessions MUST be torn down. This
+            // does NOT include VPNs.
+            final ConnectivityManager cm = ConnectivityManager.from(mContext);
+            cm.requestNetwork(cm.getDefaultRequest(), mNetworkCallback);
+        }
+
+        private boolean isActiveNetwork(@Nullable Network network) {
+            return Objects.equals(mActiveNetwork, network) && mIsRunning;
+        }
+
+        /**
+         * Called when an IKE Child session has been opened, signalling completion of the startup.
+         *
+         * <p>This method is only ever called once per IkeSession, and MUST run on the mExecutor
+         * thread in order to ensure consistency of the Ikev2VpnRunner fields.
+         */
+        public void onChildOpened(
+                @NonNull Network network, @NonNull ChildSessionConfiguration childConfig) {
+            if (!isActiveNetwork(network)) {
+                Log.d(TAG, "onOpened called for obsolete network " + network);
+
+                // Do nothing; this signals that either: (1) a new/better Network was found,
+                // and the Ikev2VpnRunner has switched to it in onDefaultNetworkChanged, or (2) this
+                // IKE session was already shut down (exited, or an error was encountered somewhere
+                // else). In both cases, all resources and sessions are torn down via
+                // resetIkeState().
+                return;
+            }
+
+            try {
+                final String interfaceName = mTunnelIface.getInterfaceName();
+                final int maxMtu = mProfile.getMaxMtu();
+                final List<LinkAddress> internalAddresses = childConfig.getInternalAddresses();
+
+                final Collection<RouteInfo> newRoutes = VpnIkev2Utils.getRoutesFromTrafficSelectors(
+                        childConfig.getOutboundTrafficSelectors());
+                for (final LinkAddress address : internalAddresses) {
+                    mTunnelIface.addAddress(address.getAddress(), address.getPrefixLength());
+                }
+
+                final NetworkAgent networkAgent;
+                final LinkProperties lp;
+
+                synchronized (Vpn.this) {
+                    mInterface = interfaceName;
+                    mConfig.mtu = maxMtu;
+                    mConfig.interfaze = mInterface;
+
+                    mConfig.addresses.clear();
+                    mConfig.addresses.addAll(internalAddresses);
+
+                    mConfig.routes.clear();
+                    mConfig.routes.addAll(newRoutes);
+
+                    // TODO: Add DNS servers from negotiation
+
+                    networkAgent = mNetworkAgent;
+
+                    // The below must be done atomically with the mConfig update, otherwise
+                    // isRunningLocked() will be racy.
+                    if (networkAgent == null) {
+                        if (isSettingsVpnLocked()) {
+                            prepareStatusIntent();
+                        }
+                        agentConnect();
+                        return; // Link properties are already sent.
+                    }
+
+                    lp = makeLinkProperties(); // Accesses VPN instance fields; must be locked
+                }
+
+                networkAgent.sendLinkProperties(lp);
+            } catch (Exception e) {
+                Log.d(TAG, "Error in ChildOpened for network " + network, e);
+                onSessionLost(network);
+            }
+        }
+
+        /**
+         * Called when an IPsec transform has been created, and should be applied.
+         *
+         * <p>This method is called multiple times over the lifetime of an IkeSession (or default
+         * network), and is MUST always be called on the mExecutor thread in order to ensure
+         * consistency of the Ikev2VpnRunner fields.
+         */
+        public void onChildTransformCreated(
+                @NonNull Network network, @NonNull IpSecTransform transform, int direction) {
+            if (!isActiveNetwork(network)) {
+                Log.d(TAG, "ChildTransformCreated for obsolete network " + network);
+
+                // Do nothing; this signals that either: (1) a new/better Network was found,
+                // and the Ikev2VpnRunner has switched to it in onDefaultNetworkChanged, or (2) this
+                // IKE session was already shut down (exited, or an error was encountered somewhere
+                // else). In both cases, all resources and sessions are torn down via
+                // resetIkeState().
+                return;
+            }
+
+            try {
+                // Transforms do not need to be persisted; the IkeSession will keep
+                // them alive for us
+                mIpSecManager.applyTunnelModeTransform(mTunnelIface, direction, transform);
+            } catch (IOException e) {
+                Log.d(TAG, "Transform application failed for network " + network, e);
+                onSessionLost(network);
+            }
+        }
+
+        /**
+         * Called when a new default network is connected.
+         *
+         * <p>The Ikev2VpnRunner will unconditionally switch to the new network, killing the old IKE
+         * state in the process, and starting a new IkeSession instance.
+         *
+         * <p>This method is called multiple times over the lifetime of the Ikev2VpnRunner, and is
+         * called on the ConnectivityService thread. Thus, the actual work MUST be proxied to the
+         * mExecutor thread in order to ensure consistency of the Ikev2VpnRunner fields.
+         */
+        public void onDefaultNetworkChanged(@NonNull Network network) {
+            Log.d(TAG, "Starting IKEv2/IPsec session on new network: " + network);
+
+            // Proxy to the Ikev2VpnRunner (single-thread) executor to ensure consistency in lieu
+            // of locking.
+            mExecutor.execute(() -> {
+                try {
+                    if (!mIsRunning) {
+                        Log.d(TAG, "onDefaultNetworkChanged after exit");
+                        return; // VPN has been shut down.
+                    }
+
+                    // Without MOBIKE, we have no way to seamlessly migrate. Close on old
+                    // (non-default) network, and start the new one.
+                    resetIkeState();
+                    mActiveNetwork = network;
+
+                    // TODO(b/149356682): Update this based on new IKE API
+                    mEncapSocket = mIpSecManager.openUdpEncapsulationSocket();
+
+                    // TODO(b/149356682): Update this based on new IKE API
+                    final IkeSessionParams ikeSessionParams =
+                            VpnIkev2Utils.buildIkeSessionParams(mProfile, mEncapSocket);
+                    final ChildSessionParams childSessionParams =
+                            VpnIkev2Utils.buildChildSessionParams();
+
+                    // TODO: Remove the need for adding two unused addresses with
+                    // IPsec tunnels.
+                    mTunnelIface =
+                            mIpSecManager.createIpSecTunnelInterface(
+                                    ikeSessionParams.getServerAddress() /* unused */,
+                                    ikeSessionParams.getServerAddress() /* unused */,
+                                    network);
+                    mNetd.setInterfaceUp(mTunnelIface.getInterfaceName());
+
+                    // Socket must be bound to prevent network switches from causing
+                    // the IKE teardown to fail/timeout.
+                    // TODO(b/149356682): Update this based on new IKE API
+                    network.bindSocket(mEncapSocket.getFileDescriptor());
+
+                    mSession = mIkev2SessionCreator.createIkeSession(
+                            mContext,
+                            ikeSessionParams,
+                            childSessionParams,
+                            mExecutor,
+                            new VpnIkev2Utils.IkeSessionCallbackImpl(
+                                    TAG, IkeV2VpnRunner.this, network),
+                            new VpnIkev2Utils.ChildSessionCallbackImpl(
+                                    TAG, IkeV2VpnRunner.this, network));
+                    Log.d(TAG, "Ike Session started for network " + network);
+                } catch (Exception e) {
+                    Log.i(TAG, "Setup failed for network " + network + ". Aborting", e);
+                    onSessionLost(network);
+                }
+            });
+        }
+
+        /**
+         * Handles loss of a session
+         *
+         * <p>The loss of a session might be due to an onLost() call, the IKE session getting torn
+         * down for any reason, or an error in updating state (transform application, VPN setup)
+         *
+         * <p>This method MUST always be called on the mExecutor thread in order to ensure
+         * consistency of the Ikev2VpnRunner fields.
+         */
+        public void onSessionLost(@NonNull Network network) {
+            if (!isActiveNetwork(network)) {
+                Log.d(TAG, "onSessionLost() called for obsolete network " + network);
+
+                // Do nothing; this signals that either: (1) a new/better Network was found,
+                // and the Ikev2VpnRunner has switched to it in onDefaultNetworkChanged, or (2) this
+                // IKE session was already shut down (exited, or an error was encountered somewhere
+                // else). In both cases, all resources and sessions are torn down via
+                // onSessionLost() and resetIkeState().
+                return;
+            }
+
+            mActiveNetwork = null;
+
+            // Close all obsolete state, but keep VPN alive incase a usable network comes up.
+            // (Mirrors VpnService behavior)
+            Log.d(TAG, "Resetting state for network: " + network);
+
+            synchronized (Vpn.this) {
+                // Since this method handles non-fatal errors only, set mInterface to null to
+                // prevent the NetworkManagementEventObserver from killing this VPN based on the
+                // interface going down (which we expect).
+                mInterface = null;
+                mConfig.interfaze = null;
+
+                // Set as unroutable to prevent traffic leaking while the interface is down.
+                if (mConfig != null && mConfig.routes != null) {
+                    final List<RouteInfo> oldRoutes = new ArrayList<>(mConfig.routes);
+
+                    mConfig.routes.clear();
+                    for (final RouteInfo route : oldRoutes) {
+                        mConfig.routes.add(new RouteInfo(route.getDestination(), RTN_UNREACHABLE));
+                    }
+                    if (mNetworkAgent != null) {
+                        mNetworkAgent.sendLinkProperties(makeLinkProperties());
+                    }
+                }
+            }
+
+            resetIkeState();
+        }
+
+        /**
+         * Cleans up all IKE state
+         *
+         * <p>This method MUST always be called on the mExecutor thread in order to ensure
+         * consistency of the Ikev2VpnRunner fields.
+         */
+        private void resetIkeState() {
+            if (mTunnelIface != null) {
+                // No need to call setInterfaceDown(); the IpSecInterface is being fully torn down.
+                mTunnelIface.close();
+                mTunnelIface = null;
+            }
+            if (mSession != null) {
+                mSession.kill(); // Kill here to make sure all resources are released immediately
+                mSession = null;
+            }
+
+            // TODO(b/149356682): Update this based on new IKE API
+            if (mEncapSocket != null) {
+                try {
+                    mEncapSocket.close();
+                } catch (IOException e) {
+                    Log.e(TAG, "Failed to close encap socket", e);
+                }
+                mEncapSocket = null;
+            }
+        }
+
+        /**
+         * Triggers cleanup of outer class' state
+         *
+         * <p>Can be called from any thread, as it does not mutate state in the Ikev2VpnRunner.
+         */
+        private void cleanupVpnState() {
+            synchronized (Vpn.this) {
+                agentDisconnect();
+            }
+        }
+
+        /**
+         * Cleans up all Ikev2VpnRunner internal state
+         *
+         * <p>This method MUST always be called on the mExecutor thread in order to ensure
+         * consistency of the Ikev2VpnRunner fields.
+         */
+        private void shutdownVpnRunner() {
+            mActiveNetwork = null;
+            mIsRunning = false;
+
+            resetIkeState();
+
+            final ConnectivityManager cm = ConnectivityManager.from(mContext);
+            cm.unregisterNetworkCallback(mNetworkCallback);
+
+            mExecutor.shutdown();
         }
 
         @Override
         public void exit() {
-            // TODO: Teardown IKE session & any resources.
-            agentDisconnect();
+            // Cleanup outer class' state immediately, otherwise race conditions may ensue.
+            cleanupVpnState();
+
+            mExecutor.execute(() -> {
+                shutdownVpnRunner();
+            });
         }
     }
 
@@ -2488,12 +2850,46 @@
                         throw new IllegalArgumentException("No profile found for " + packageName);
                     }
 
-                    startVpnProfilePrivileged(profile);
+                    startVpnProfilePrivileged(profile, packageName);
                 });
     }
 
-    private void startVpnProfilePrivileged(@NonNull VpnProfile profile) {
-        // TODO: Start PlatformVpnRunner
+    private void startVpnProfilePrivileged(
+            @NonNull VpnProfile profile, @NonNull String packageName) {
+        // Ensure that no other previous instance is running.
+        if (mVpnRunner != null) {
+            mVpnRunner.exit();
+            mVpnRunner = null;
+        }
+        updateState(DetailedState.CONNECTING, "startPlatformVpn");
+
+        try {
+            // Build basic config
+            mConfig = new VpnConfig();
+            mConfig.user = packageName;
+            mConfig.isMetered = profile.isMetered;
+            mConfig.startTime = SystemClock.elapsedRealtime();
+            mConfig.proxyInfo = profile.proxy;
+
+            switch (profile.type) {
+                case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
+                case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
+                case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
+                    mVpnRunner = new IkeV2VpnRunner(Ikev2VpnProfile.fromVpnProfile(profile));
+                    mVpnRunner.start();
+                    break;
+                default:
+                    updateState(DetailedState.FAILED, "Invalid platform VPN type");
+                    Log.d(TAG, "Unknown VPN profile type: " + profile.type);
+                    break;
+            }
+        } catch (IOException | GeneralSecurityException e) {
+            // Reset mConfig
+            mConfig = null;
+
+            updateState(DetailedState.FAILED, "VPN startup failed");
+            throw new IllegalArgumentException("VPN startup failed", e);
+        }
     }
 
     /**
@@ -2507,13 +2903,37 @@
     public synchronized void stopVpnProfile(@NonNull String packageName) {
         checkNotNull(packageName, "No package name provided");
 
-        // To stop the VPN profile, the caller must be the current prepared package. Otherwise,
-        // the app is not prepared, and we can just return.
-        if (!isCurrentPreparedPackage(packageName)) {
-            // TODO: Also check to make sure that the running VPN is a VPN profile.
+        // To stop the VPN profile, the caller must be the current prepared package and must be
+        // running an Ikev2VpnProfile.
+        if (!isCurrentPreparedPackage(packageName) && mVpnRunner instanceof IkeV2VpnRunner) {
             return;
         }
 
         prepareInternal(VpnConfig.LEGACY_VPN);
     }
+
+    /**
+     * Proxy to allow testing
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    public static class Ikev2SessionCreator {
+        /** Creates a IKE session */
+        public IkeSession createIkeSession(
+                @NonNull Context context,
+                @NonNull IkeSessionParams ikeSessionParams,
+                @NonNull ChildSessionParams firstChildSessionParams,
+                @NonNull Executor userCbExecutor,
+                @NonNull IkeSessionCallback ikeSessionCallback,
+                @NonNull ChildSessionCallback firstChildSessionCallback) {
+            return new IkeSession(
+                    context,
+                    ikeSessionParams,
+                    firstChildSessionParams,
+                    userCbExecutor,
+                    ikeSessionCallback,
+                    firstChildSessionCallback);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java b/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java
new file mode 100644
index 0000000..33fc32b
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java
@@ -0,0 +1,390 @@
+/*
+ * 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.connectivity;
+
+import static android.net.ConnectivityManager.NetworkCallback;
+import static android.net.ipsec.ike.SaProposal.DH_GROUP_1024_BIT_MODP;
+import static android.net.ipsec.ike.SaProposal.DH_GROUP_2048_BIT_MODP;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_CBC;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_16;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_AES_XCBC_96;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_256_128;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_384_192;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_512_256;
+import static android.net.ipsec.ike.SaProposal.KEY_LEN_AES_128;
+import static android.net.ipsec.ike.SaProposal.KEY_LEN_AES_192;
+import static android.net.ipsec.ike.SaProposal.KEY_LEN_AES_256;
+import static android.net.ipsec.ike.SaProposal.PSEUDORANDOM_FUNCTION_AES128_XCBC;
+import static android.net.ipsec.ike.SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1;
+
+import android.annotation.NonNull;
+import android.net.Ikev2VpnProfile;
+import android.net.InetAddresses;
+import android.net.IpPrefix;
+import android.net.IpSecManager.UdpEncapsulationSocket;
+import android.net.IpSecTransform;
+import android.net.Network;
+import android.net.RouteInfo;
+import android.net.eap.EapSessionConfig;
+import android.net.ipsec.ike.ChildSaProposal;
+import android.net.ipsec.ike.ChildSessionCallback;
+import android.net.ipsec.ike.ChildSessionConfiguration;
+import android.net.ipsec.ike.ChildSessionParams;
+import android.net.ipsec.ike.IkeFqdnIdentification;
+import android.net.ipsec.ike.IkeIdentification;
+import android.net.ipsec.ike.IkeIpv4AddrIdentification;
+import android.net.ipsec.ike.IkeIpv6AddrIdentification;
+import android.net.ipsec.ike.IkeKeyIdIdentification;
+import android.net.ipsec.ike.IkeRfc822AddrIdentification;
+import android.net.ipsec.ike.IkeSaProposal;
+import android.net.ipsec.ike.IkeSessionCallback;
+import android.net.ipsec.ike.IkeSessionConfiguration;
+import android.net.ipsec.ike.IkeSessionParams;
+import android.net.ipsec.ike.IkeTrafficSelector;
+import android.net.ipsec.ike.TunnelModeChildSessionParams;
+import android.net.ipsec.ike.exceptions.IkeException;
+import android.net.ipsec.ike.exceptions.IkeProtocolException;
+import android.net.util.IpRange;
+import android.system.OsConstants;
+import android.util.Log;
+
+import com.android.internal.net.VpnProfile;
+import com.android.internal.util.HexDump;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Utility class to build and convert IKEv2/IPsec parameters.
+ *
+ * @hide
+ */
+public class VpnIkev2Utils {
+    static IkeSessionParams buildIkeSessionParams(
+            @NonNull Ikev2VpnProfile profile, @NonNull UdpEncapsulationSocket socket) {
+        // TODO(b/149356682): Update this based on new IKE API. Only numeric addresses supported
+        //                    until then. All others throw IAE (caught by caller).
+        final InetAddress serverAddr = InetAddresses.parseNumericAddress(profile.getServerAddr());
+        final IkeIdentification localId = parseIkeIdentification(profile.getUserIdentity());
+        final IkeIdentification remoteId = parseIkeIdentification(profile.getServerAddr());
+
+        // TODO(b/149356682): Update this based on new IKE API.
+        final IkeSessionParams.Builder ikeOptionsBuilder =
+                new IkeSessionParams.Builder()
+                        .setServerAddress(serverAddr)
+                        .setUdpEncapsulationSocket(socket)
+                        .setLocalIdentification(localId)
+                        .setRemoteIdentification(remoteId);
+        setIkeAuth(profile, ikeOptionsBuilder);
+
+        for (final IkeSaProposal ikeProposal : getIkeSaProposals()) {
+            ikeOptionsBuilder.addSaProposal(ikeProposal);
+        }
+
+        return ikeOptionsBuilder.build();
+    }
+
+    static ChildSessionParams buildChildSessionParams() {
+        final TunnelModeChildSessionParams.Builder childOptionsBuilder =
+                new TunnelModeChildSessionParams.Builder();
+
+        for (final ChildSaProposal childProposal : getChildSaProposals()) {
+            childOptionsBuilder.addSaProposal(childProposal);
+        }
+
+        childOptionsBuilder.addInternalAddressRequest(OsConstants.AF_INET);
+        childOptionsBuilder.addInternalAddressRequest(OsConstants.AF_INET6);
+        childOptionsBuilder.addInternalDnsServerRequest(OsConstants.AF_INET);
+        childOptionsBuilder.addInternalDnsServerRequest(OsConstants.AF_INET6);
+
+        return childOptionsBuilder.build();
+    }
+
+    private static void setIkeAuth(
+            @NonNull Ikev2VpnProfile profile, @NonNull IkeSessionParams.Builder builder) {
+        switch (profile.getType()) {
+            case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
+                final EapSessionConfig eapConfig =
+                        new EapSessionConfig.Builder()
+                                .setEapMsChapV2Config(profile.getUsername(), profile.getPassword())
+                                .build();
+                builder.setAuthEap(profile.getServerRootCaCert(), eapConfig);
+                break;
+            case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
+                builder.setAuthPsk(profile.getPresharedKey());
+                break;
+            case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
+                builder.setAuthDigitalSignature(
+                        profile.getServerRootCaCert(),
+                        profile.getUserCert(),
+                        profile.getRsaPrivateKey());
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown auth method set");
+        }
+    }
+
+    private static List<IkeSaProposal> getIkeSaProposals() {
+        // TODO: filter this based on allowedAlgorithms
+        final List<IkeSaProposal> proposals = new ArrayList<>();
+
+        // Encryption Algorithms: Currently only AES_CBC is supported.
+        final IkeSaProposal.Builder normalModeBuilder = new IkeSaProposal.Builder();
+
+        // Currently only AES_CBC is supported.
+        normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_256);
+        normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_192);
+        normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_128);
+
+        // Authentication/Integrity Algorithms
+        normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_512_256);
+        normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_384_192);
+        normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_256_128);
+        normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_AES_XCBC_96);
+        normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA1_96);
+
+        // Add AEAD options
+        final IkeSaProposal.Builder aeadBuilder = new IkeSaProposal.Builder();
+        aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_256);
+        aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_256);
+        aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_256);
+        aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_192);
+        aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_192);
+        aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_192);
+        aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_128);
+        aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_128);
+        aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_128);
+
+        // Add dh, prf for both builders
+        for (final IkeSaProposal.Builder builder : Arrays.asList(normalModeBuilder, aeadBuilder)) {
+            builder.addDhGroup(DH_GROUP_2048_BIT_MODP);
+            builder.addDhGroup(DH_GROUP_1024_BIT_MODP);
+            builder.addPseudorandomFunction(PSEUDORANDOM_FUNCTION_AES128_XCBC);
+            builder.addPseudorandomFunction(PSEUDORANDOM_FUNCTION_HMAC_SHA1);
+        }
+
+        proposals.add(normalModeBuilder.build());
+        proposals.add(aeadBuilder.build());
+        return proposals;
+    }
+
+    private static List<ChildSaProposal> getChildSaProposals() {
+        // TODO: filter this based on allowedAlgorithms
+        final List<ChildSaProposal> proposals = new ArrayList<>();
+
+        // Add non-AEAD options
+        final ChildSaProposal.Builder normalModeBuilder = new ChildSaProposal.Builder();
+
+        // Encryption Algorithms: Currently only AES_CBC is supported.
+        normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_256);
+        normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_192);
+        normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_128);
+
+        // Authentication/Integrity Algorithms
+        normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_512_256);
+        normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_384_192);
+        normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_256_128);
+        normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA1_96);
+
+        // Add AEAD options
+        final ChildSaProposal.Builder aeadBuilder = new ChildSaProposal.Builder();
+        aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_256);
+        aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_256);
+        aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_256);
+        aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_192);
+        aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_192);
+        aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_192);
+        aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_128);
+        aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_128);
+        aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_128);
+
+        proposals.add(normalModeBuilder.build());
+        proposals.add(aeadBuilder.build());
+        return proposals;
+    }
+
+    static class IkeSessionCallbackImpl implements IkeSessionCallback {
+        private final String mTag;
+        private final Vpn.IkeV2VpnRunnerCallback mCallback;
+        private final Network mNetwork;
+
+        IkeSessionCallbackImpl(String tag, Vpn.IkeV2VpnRunnerCallback callback, Network network) {
+            mTag = tag;
+            mCallback = callback;
+            mNetwork = network;
+        }
+
+        @Override
+        public void onOpened(@NonNull IkeSessionConfiguration ikeSessionConfig) {
+            Log.d(mTag, "IkeOpened for network " + mNetwork);
+            // Nothing to do here.
+        }
+
+        @Override
+        public void onClosed() {
+            Log.d(mTag, "IkeClosed for network " + mNetwork);
+            mCallback.onSessionLost(mNetwork); // Server requested session closure. Retry?
+        }
+
+        @Override
+        public void onClosedExceptionally(@NonNull IkeException exception) {
+            Log.d(mTag, "IkeClosedExceptionally for network " + mNetwork, exception);
+            mCallback.onSessionLost(mNetwork);
+        }
+
+        @Override
+        public void onError(@NonNull IkeProtocolException exception) {
+            Log.d(mTag, "IkeError for network " + mNetwork, exception);
+            // Non-fatal, log and continue.
+        }
+    }
+
+    static class ChildSessionCallbackImpl implements ChildSessionCallback {
+        private final String mTag;
+        private final Vpn.IkeV2VpnRunnerCallback mCallback;
+        private final Network mNetwork;
+
+        ChildSessionCallbackImpl(String tag, Vpn.IkeV2VpnRunnerCallback callback, Network network) {
+            mTag = tag;
+            mCallback = callback;
+            mNetwork = network;
+        }
+
+        @Override
+        public void onOpened(@NonNull ChildSessionConfiguration childConfig) {
+            Log.d(mTag, "ChildOpened for network " + mNetwork);
+            mCallback.onChildOpened(mNetwork, childConfig);
+        }
+
+        @Override
+        public void onClosed() {
+            Log.d(mTag, "ChildClosed for network " + mNetwork);
+            mCallback.onSessionLost(mNetwork);
+        }
+
+        @Override
+        public void onClosedExceptionally(@NonNull IkeException exception) {
+            Log.d(mTag, "ChildClosedExceptionally for network " + mNetwork, exception);
+            mCallback.onSessionLost(mNetwork);
+        }
+
+        @Override
+        public void onIpSecTransformCreated(@NonNull IpSecTransform transform, int direction) {
+            Log.d(mTag, "ChildTransformCreated; Direction: " + direction + "; network " + mNetwork);
+            mCallback.onChildTransformCreated(mNetwork, transform, direction);
+        }
+
+        @Override
+        public void onIpSecTransformDeleted(@NonNull IpSecTransform transform, int direction) {
+            // Nothing to be done; no references to the IpSecTransform are held by the
+            // Ikev2VpnRunner (or this callback class), and this transform will be closed by the
+            // IKE library.
+            Log.d(mTag,
+                    "ChildTransformDeleted; Direction: " + direction + "; for network " + mNetwork);
+        }
+    }
+
+    static class Ikev2VpnNetworkCallback extends NetworkCallback {
+        private final String mTag;
+        private final Vpn.IkeV2VpnRunnerCallback mCallback;
+
+        Ikev2VpnNetworkCallback(String tag, Vpn.IkeV2VpnRunnerCallback callback) {
+            mTag = tag;
+            mCallback = callback;
+        }
+
+        @Override
+        public void onAvailable(@NonNull Network network) {
+            Log.d(mTag, "Starting IKEv2/IPsec session on new network: " + network);
+            mCallback.onDefaultNetworkChanged(network);
+        }
+
+        @Override
+        public void onLost(@NonNull Network network) {
+            Log.d(mTag, "Tearing down; lost network: " + network);
+            mCallback.onSessionLost(network);
+        }
+    }
+
+    /**
+     * Identity parsing logic using similar logic to open source implementations of IKEv2
+     *
+     * <p>This method does NOT support using type-prefixes (eg 'fqdn:' or 'keyid'), or ASN.1 encoded
+     * identities.
+     */
+    private static IkeIdentification parseIkeIdentification(@NonNull String identityStr) {
+        // TODO: Add identity formatting to public API javadocs.
+        if (identityStr.contains("@")) {
+            if (identityStr.startsWith("@#")) {
+                // KEY_ID
+                final String hexStr = identityStr.substring(2);
+                return new IkeKeyIdIdentification(HexDump.hexStringToByteArray(hexStr));
+            } else if (identityStr.startsWith("@@")) {
+                // RFC822 (USER_FQDN)
+                return new IkeRfc822AddrIdentification(identityStr.substring(2));
+            } else if (identityStr.startsWith("@")) {
+                // FQDN
+                return new IkeFqdnIdentification(identityStr.substring(1));
+            } else {
+                // RFC822 (USER_FQDN)
+                return new IkeRfc822AddrIdentification(identityStr);
+            }
+        } else if (InetAddresses.isNumericAddress(identityStr)) {
+            final InetAddress addr = InetAddresses.parseNumericAddress(identityStr);
+            if (addr instanceof Inet4Address) {
+                // IPv4
+                return new IkeIpv4AddrIdentification((Inet4Address) addr);
+            } else if (addr instanceof Inet6Address) {
+                // IPv6
+                return new IkeIpv6AddrIdentification((Inet6Address) addr);
+            } else {
+                throw new IllegalArgumentException("IP version not supported");
+            }
+        } else {
+            if (identityStr.contains(":")) {
+                // KEY_ID
+                return new IkeKeyIdIdentification(identityStr.getBytes());
+            } else {
+                // FQDN
+                return new IkeFqdnIdentification(identityStr);
+            }
+        }
+    }
+
+    static Collection<RouteInfo> getRoutesFromTrafficSelectors(
+            List<IkeTrafficSelector> trafficSelectors) {
+        final HashSet<RouteInfo> routes = new HashSet<>();
+
+        for (final IkeTrafficSelector selector : trafficSelectors) {
+            for (final IpPrefix prefix :
+                    new IpRange(selector.startingAddress, selector.endingAddress).asIpPrefixes()) {
+                routes.add(new RouteInfo(prefix, null));
+            }
+        }
+
+        return routes;
+    }
+}
diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java
index dea47db..afdcda9 100644
--- a/services/core/java/com/android/server/content/SyncStorageEngine.java
+++ b/services/core/java/com/android/server/content/SyncStorageEngine.java
@@ -1738,7 +1738,7 @@
 
     /**
      * Ensure the old pending.bin is deleted, as it has been changed to pending.xml.
-     * pending.xml was used starting in KLP.
+     * pending.xml was used starting in KitKat.
      * @param syncDir directory where the sync files are located.
      */
     private void maybeDeleteLegacyPendingInfoLocked(File syncDir) {
diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
index 68ced79..b9a30bb 100644
--- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
+++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
@@ -23,6 +23,7 @@
 import static android.content.integrity.AppIntegrityManager.EXTRA_STATUS;
 import static android.content.integrity.AppIntegrityManager.STATUS_FAILURE;
 import static android.content.integrity.AppIntegrityManager.STATUS_SUCCESS;
+import static android.content.integrity.InstallerAllowedByManifestFormula.INSTALLER_CERTIFICATE_NOT_EVALUATED;
 import static android.content.integrity.IntegrityUtils.getHexDigest;
 import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID;
 
@@ -95,7 +96,7 @@
      * This string will be used as the "installer" for formula evaluation when the app is being
      * installed via ADB.
      */
-    private static final String ADB_INSTALLER = "adb";
+    public static final String ADB_INSTALLER = "adb";
 
     private static final String TAG = "AppIntegrityManagerServiceImpl";
 
@@ -106,8 +107,6 @@
     private static final String ALLOWED_INSTALLER_DELIMITER = ",";
     private static final String INSTALLER_PACKAGE_CERT_DELIMITER = "\\|";
 
-    private static final String INSTALLER_CERT_NOT_APPLICABLE = "";
-
     // Access to files inside mRulesDir is protected by mRulesLock;
     private final Context mContext;
     private final Handler mHandler;
@@ -282,15 +281,16 @@
             builder.setInstallerName(getPackageNameNormalized(installerPackageName));
             builder.setInstallerCertificates(installerCertificates);
             builder.setIsPreInstalled(isSystemApp(packageName));
+            builder.setAllowedInstallersAndCert(getAllowedInstallers(packageInfo));
 
             AppInstallMetadata appInstallMetadata = builder.build();
-            Map<String, String> allowedInstallers = getAllowedInstallers(packageInfo);
 
             Slog.i(
                     TAG,
-                    "To be verified: " + appInstallMetadata + " installers " + allowedInstallers);
+                    "To be verified: " + appInstallMetadata + " installers " + getAllowedInstallers(
+                            packageInfo));
             IntegrityCheckResult result =
-                    mEvaluationEngine.evaluate(appInstallMetadata, allowedInstallers);
+                    mEvaluationEngine.evaluate(appInstallMetadata);
             Slog.i(
                     TAG,
                     "Integrity check result: "
@@ -449,9 +449,9 @@
                         String packageName = getPackageNameNormalized(packageAndCert[0]);
                         String cert = packageAndCert[1];
                         packageCertMap.put(packageName, cert);
-                    } else if (packageAndCert.length == 1
-                            && packageAndCert[0].equals(ADB_INSTALLER)) {
-                        packageCertMap.put(ADB_INSTALLER, INSTALLER_CERT_NOT_APPLICABLE);
+                    } else if (packageAndCert.length == 1) {
+                        packageCertMap.put(getPackageNameNormalized(packageAndCert[0]),
+                                INSTALLER_CERTIFICATE_NOT_EVALUATED);
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java b/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java
index 79e69e1..61da45d 100644
--- a/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java
+++ b/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java
@@ -17,9 +17,6 @@
 package com.android.server.integrity.engine;
 
 import android.content.integrity.AppInstallMetadata;
-import android.content.integrity.AtomicFormula;
-import android.content.integrity.CompoundFormula;
-import android.content.integrity.IntegrityFormula;
 import android.content.integrity.Rule;
 import android.util.Slog;
 
@@ -28,10 +25,8 @@
 import com.android.server.integrity.model.IntegrityCheckResult;
 
 import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
-import java.util.Map;
-import java.util.Optional;
 
 /**
  * The engine used to evaluate rules against app installs.
@@ -69,16 +64,15 @@
      * @return result of the integrity check
      */
     public IntegrityCheckResult evaluate(
-            AppInstallMetadata appInstallMetadata, Map<String, String> allowedInstallers) {
+            AppInstallMetadata appInstallMetadata) {
         List<Rule> rules = loadRules(appInstallMetadata);
-        allowedInstallersRule(allowedInstallers).ifPresent(rules::add);
         return RuleEvaluator.evaluateRules(rules, appInstallMetadata);
     }
 
     private List<Rule> loadRules(AppInstallMetadata appInstallMetadata) {
         if (!mIntegrityFileManager.initialized()) {
-            Slog.w(TAG, "Integrity rule files are not available. Evaluating only manifest rules.");
-            return new ArrayList<>();
+            Slog.w(TAG, "Integrity rule files are not available.");
+            return Collections.emptyList();
         }
 
         try {
@@ -88,41 +82,4 @@
             return new ArrayList<>();
         }
     }
-
-    private static Optional<Rule> allowedInstallersRule(Map<String, String> allowedInstallers) {
-        if (allowedInstallers.isEmpty()) {
-            return Optional.empty();
-        }
-
-        List<IntegrityFormula> formulas = new ArrayList<>(allowedInstallers.size());
-        allowedInstallers.forEach(
-                (installer, cert) -> {
-                    formulas.add(allowedInstallerFormula(installer, cert));
-                });
-
-        // We need this special case since OR-formulas require at least two operands.
-        IntegrityFormula allInstallersFormula =
-                formulas.size() == 1
-                        ? formulas.get(0)
-                        : new CompoundFormula(CompoundFormula.OR, formulas);
-
-        return Optional.of(
-                new Rule(
-                        new CompoundFormula(
-                                CompoundFormula.NOT, Arrays.asList(allInstallersFormula)),
-                        Rule.DENY));
-    }
-
-    private static IntegrityFormula allowedInstallerFormula(String installer, String cert) {
-        return new CompoundFormula(
-                CompoundFormula.AND,
-                Arrays.asList(
-                        new AtomicFormula.StringAtomicFormula(
-                                AtomicFormula.INSTALLER_NAME,
-                                installer,
-                                /* isHashedValue= */ false),
-                        new AtomicFormula.StringAtomicFormula(
-                                AtomicFormula.INSTALLER_CERTIFICATE, cert, /* isHashedValue= */
-                                false)));
-    }
 }
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
index e991f96..c9e356e 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
@@ -150,6 +150,7 @@
     private RebootEscrowKey getAndClearRebootEscrowKey() {
         IRebootEscrow rebootEscrow = mInjector.getRebootEscrow();
         if (rebootEscrow == null) {
+            Slog.w(TAG, "Had reboot escrow data for users, but RebootEscrow HAL is unavailable");
             return null;
         }
 
@@ -197,11 +198,12 @@
 
             mCallbacks.onRebootEscrowRestored(escrowData.getSpVersion(),
                     escrowData.getSyntheticPassword(), userId);
+            Slog.i(TAG, "Restored reboot escrow data for user " + userId);
             return true;
         } catch (IOException e) {
             Slog.w(TAG, "Could not load reboot escrow data for user " + userId, e);
+            return false;
         }
-        return false;
     }
 
     void callToRebootEscrowIfNeeded(@UserIdInt int userId, byte spVersion,
@@ -212,16 +214,13 @@
 
         IRebootEscrow rebootEscrow = mInjector.getRebootEscrow();
         if (rebootEscrow == null) {
-            mRebootEscrowWanted = false;
-            setRebootEscrowReady(false);
+            Slog.w(TAG, "Reboot escrow requested, but RebootEscrow HAL is unavailable");
             return;
         }
 
         RebootEscrowKey escrowKey = generateEscrowKeyIfNeeded();
         if (escrowKey == null) {
             Slog.e(TAG, "Could not generate escrow key");
-            mRebootEscrowWanted = false;
-            setRebootEscrowReady(false);
             return;
         }
 
@@ -250,6 +249,7 @@
             try {
                 key = RebootEscrowKey.generate();
             } catch (IOException e) {
+                Slog.w(TAG, "Could not generate reboot escrow key");
                 return null;
             }
 
@@ -286,6 +286,7 @@
 
         IRebootEscrow rebootEscrow = mInjector.getRebootEscrow();
         if (rebootEscrow == null) {
+            Slog.w(TAG, "Escrow marked as ready, but RebootEscrow HAL is unavailable");
             return false;
         }
 
@@ -295,6 +296,7 @@
         }
 
         if (escrowKey == null) {
+            Slog.e(TAG, "Escrow key is null, but escrow was marked as ready");
             return false;
         }
 
@@ -302,8 +304,9 @@
         try {
             rebootEscrow.storeKey(escrowKey.getKeyBytes());
             armedRebootEscrow = true;
+            Slog.i(TAG, "Reboot escrow key stored with RebootEscrow HAL");
         } catch (RemoteException e) {
-            Slog.w(TAG, "Failed escrow secret to RebootEscrow HAL", e);
+            Slog.e(TAG, "Failed escrow secret to RebootEscrow HAL", e);
         }
         return armedRebootEscrow;
     }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 916b63b..a570502 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -932,7 +932,7 @@
                 StatusBarNotification sbn = r.getSbn();
                 cancelNotification(callingUid, callingPid, sbn.getPackageName(), sbn.getTag(),
                         sbn.getId(), Notification.FLAG_AUTO_CANCEL,
-                        FLAG_FOREGROUND_SERVICE, false, r.getUserId(),
+                        FLAG_FOREGROUND_SERVICE | FLAG_BUBBLE, false, r.getUserId(),
                         REASON_CLICK, nv.rank, nv.count, null);
                 nv.recycle();
                 reportUserInteraction(r);
@@ -7986,7 +7986,8 @@
 
                     FlagChecker flagChecker = (int flags) -> {
                         int flagsToCheck = FLAG_ONGOING_EVENT | FLAG_NO_CLEAR;
-                        if (REASON_LISTENER_CANCEL_ALL == reason) {
+                        if (REASON_LISTENER_CANCEL_ALL == reason
+                                || REASON_CANCEL_ALL == reason) {
                             flagsToCheck |= FLAG_BUBBLE;
                         }
                         if ((flags & flagsToCheck) != 0) {
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index b98bb08..8ad3e9d 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -611,10 +611,10 @@
     /**
      * Bind mount private volume CE and DE mirror storage.
      */
-    public void onPrivateVolumeMounted(String volumeUuid) throws InstallerException {
+    public void tryMountDataMirror(String volumeUuid) throws InstallerException {
         if (!checkBeforeRemote()) return;
         try {
-            mInstalld.onPrivateVolumeMounted(volumeUuid);
+            mInstalld.tryMountDataMirror(volumeUuid);
         } catch (Exception e) {
             throw InstallerException.from(e);
         }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 2c85d06..064fd3f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -4600,7 +4600,7 @@
         synchronized (mLock) {
             final AndroidPackage p = mPackages.get(packageName);
             if (p != null && p.isMatch(flags)) {
-                PackageSetting ps = getPackageSetting(p.getPackageName());
+                PackageSetting ps = getPackageSettingInternal(p.getPackageName(), callingUid);
                 if (shouldFilterApplicationLocked(ps, callingUid, userId)) {
                     return -1;
                 }
@@ -5924,7 +5924,10 @@
      */
     @Override
     public String[] getPackagesForUid(int uid) {
-        final int callingUid = Binder.getCallingUid();
+        return getPackagesForUidInternal(uid, Binder.getCallingUid());
+    }
+
+    private String[] getPackagesForUidInternal(int uid, int callingUid) {
         final boolean isCallerInstantApp = getInstantAppPackageName(callingUid) != null;
         final int userId = UserHandle.getUserId(uid);
         final int appId = UserHandle.getAppId(uid);
@@ -17380,6 +17383,13 @@
 
     @GuardedBy("mLock")
     private String resolveInternalPackageNameLPr(String packageName, long versionCode) {
+        final int callingUid = Binder.getCallingUid();
+        return resolveInternalPackageNameInternalLocked(packageName, versionCode,
+                callingUid);
+    }
+
+    private String resolveInternalPackageNameInternalLocked(
+            String packageName, long versionCode, int callingUid) {
         // Handle renamed packages
         String normalizedPackageName = mSettings.getRenamedPackageLPr(packageName);
         packageName = normalizedPackageName != null ? normalizedPackageName : packageName;
@@ -17393,12 +17403,12 @@
 
         // Figure out which lib versions the caller can see
         LongSparseLongArray versionsCallerCanSee = null;
-        final int callingAppId = UserHandle.getAppId(Binder.getCallingUid());
+        final int callingAppId = UserHandle.getAppId(callingUid);
         if (callingAppId != Process.SYSTEM_UID && callingAppId != Process.SHELL_UID
                 && callingAppId != Process.ROOT_UID) {
             versionsCallerCanSee = new LongSparseLongArray();
             String libName = versionedLib.valueAt(0).getName();
-            String[] uidPackages = getPackagesForUid(Binder.getCallingUid());
+            String[] uidPackages = getPackagesForUidInternal(callingUid, callingUid);
             if (uidPackages != null) {
                 for (String uidPackage : uidPackages) {
                     PackageSetting ps = mSettings.getPackageLPr(uidPackage);
@@ -23003,7 +23013,7 @@
         @Override
         public AndroidPackage getPackage(int uid) {
             synchronized (mLock) {
-                final String[] packageNames = getPackagesForUid(uid);
+                final String[] packageNames = getPackagesForUidInternal(uid, Process.SYSTEM_UID);
                 AndroidPackage pkg = null;
                 final int numPackages = packageNames == null ? 0 : packageNames.length;
                 for (int i = 0; pkg == null && i < numPackages; i++) {
@@ -24017,9 +24027,13 @@
 
     @Nullable
     public PackageSetting getPackageSetting(String packageName) {
+        return getPackageSettingInternal(packageName, Binder.getCallingUid());
+    }
+
+    private PackageSetting getPackageSettingInternal(String packageName, int callingUid) {
         synchronized (mLock) {
-            packageName = resolveInternalPackageNameLPr(
-                    packageName, PackageManager.VERSION_CODE_HIGHEST);
+            packageName = resolveInternalPackageNameInternalLocked(
+                    packageName, PackageManager.VERSION_CODE_HIGHEST, callingUid);
             return mSettings.mPackages.get(packageName);
         }
     }
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 002ab9c..a7b0d84 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -25,6 +25,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.SynchronousUserSwitchObserver;
@@ -95,6 +96,7 @@
 import com.android.server.ServiceThread;
 import com.android.server.SystemService;
 import com.android.server.UiThread;
+import com.android.server.UserspaceRebootLogger;
 import com.android.server.Watchdog;
 import com.android.server.am.BatteryStatsService;
 import com.android.server.lights.LightsManager;
@@ -2486,7 +2488,8 @@
         boolean changed = false;
         if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_USER_ACTIVITY | DIRTY_BOOT_COMPLETED
                 | DIRTY_WAKEFULNESS | DIRTY_STAY_ON | DIRTY_PROXIMITY_POSITIVE
-                | DIRTY_DOCK_STATE | DIRTY_ATTENTIVE)) != 0) {
+                | DIRTY_DOCK_STATE | DIRTY_ATTENTIVE | DIRTY_SETTINGS
+                | DIRTY_SCREEN_BRIGHTNESS_BOOST)) != 0) {
             if (mWakefulness == WAKEFULNESS_AWAKE && isItBedTimeYetLocked()) {
                 if (DEBUG_SPEW) {
                     Slog.d(TAG, "updateWakefulnessLocked: Bed time...");
@@ -3153,7 +3156,10 @@
     }
 
     private void shutdownOrRebootInternal(final @HaltMode int haltMode, final boolean confirm,
-            final String reason, boolean wait) {
+            @Nullable final String reason, boolean wait) {
+        if (PowerManager.REBOOT_USERSPACE.equals(reason)) {
+            UserspaceRebootLogger.noteUserspaceRebootWasRequested();
+        }
         if (mHandler == null || !mSystemReady) {
             if (RescueParty.isAttemptingFactoryReset()) {
                 // If we're stuck in a really low-level reboot loop, and a
@@ -5038,7 +5044,7 @@
          * @param wait If true, this call waits for the reboot to complete and does not return.
          */
         @Override // Binder call
-        public void reboot(boolean confirm, String reason, boolean wait) {
+        public void reboot(boolean confirm, @Nullable String reason, boolean wait) {
             mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null);
             if (PowerManager.REBOOT_RECOVERY.equals(reason)
                     || PowerManager.REBOOT_RECOVERY_UPDATE.equals(reason)) {
diff --git a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
index 7164a30..e0701e8 100644
--- a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
+++ b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
@@ -359,20 +359,23 @@
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
 
         if (!mPreparedForReboot) {
+            Slog.i(TAG, "Reboot requested before prepare completed");
             return false;
         }
 
-        if (updateToken != null && updateToken.equals(mUnattendedRebootToken)) {
-            if (!mInjector.getLockSettingsService().armRebootEscrow()) {
-                return false;
-            }
-
-            PowerManager pm = mInjector.getPowerManager();
-            pm.reboot(reason);
-            return true;
+        if (updateToken != null && !updateToken.equals(mUnattendedRebootToken)) {
+            Slog.i(TAG, "Reboot requested after preparation, but with mismatching token");
+            return false;
         }
 
-        return false;
+        if (!mInjector.getLockSettingsService().armRebootEscrow()) {
+            Slog.w(TAG, "Failure to escrow key for reboot");
+            return false;
+        }
+
+        PowerManager pm = mInjector.getPowerManager();
+        pm.reboot(reason);
+        return true;
     }
 
     /**
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 91e7cc9..9e150fd 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -869,14 +869,6 @@
             return false;
         }
 
-        ApplicationInfo appInfo = pkgInfo.applicationInfo;
-        boolean success = rollback.enableForPackage(packageName, newPackage.versionCode,
-                pkgInfo.getLongVersionCode(), isApex, appInfo.sourceDir,
-                appInfo.splitSourceDirs, session.rollbackDataPolicy);
-        if (!success) {
-            return success;
-        }
-
         if (isApex) {
             // Check if this apex contains apks inside it. If true, then they should be added as
             // a RollbackPackageInfo into this rollback
@@ -894,12 +886,24 @@
                     Slog.e(TAG, apkInApex + " is not installed");
                     return false;
                 }
-                success = rollback.enableForPackageInApex(
-                        apkInApex, apkPkgInfo.getLongVersionCode(), session.rollbackDataPolicy);
-                if (!success) return success;
+                if (!rollback.enableForPackageInApex(
+                        apkInApex, apkPkgInfo.getLongVersionCode(), session.rollbackDataPolicy)) {
+                    return false;
+                }
             }
         }
-        return true;
+
+        /**
+         * The order is important here! Always enable the embedded apk-in-apex (if any) before
+         * enabling the embedding apex. Otherwise the rollback object might be in an inconsistent
+         * state where an embedding apex is successfully enabled while one of its embedded
+         * apk-in-apex failed. Note {@link Rollback#allPackagesEnabled()} won't behave correctly if
+         * a rollback object is inconsistent because it doesn't count apk-in-apex.
+         */
+        ApplicationInfo appInfo = pkgInfo.applicationInfo;
+        return rollback.enableForPackage(packageName, newPackage.versionCode,
+                pkgInfo.getLongVersionCode(), isApex, appInfo.sourceDir,
+                appInfo.splitSourceDirs, session.rollbackDataPolicy);
     }
 
     @Override
@@ -982,8 +986,6 @@
             if (!session.isMultiPackage()) {
                 if (!enableRollbackForPackageSession(newRollback, session)) {
                     Slog.e(TAG, "Unable to enable rollback for session: " + sessionId);
-                    result.offer(-1);
-                    return;
                 }
             } else {
                 for (int childSessionId : session.getChildSessionIds()) {
@@ -991,13 +993,11 @@
                             installer.getSessionInfo(childSessionId);
                     if (childSession == null) {
                         Slog.e(TAG, "No matching child install session for: " + childSessionId);
-                        result.offer(-1);
-                        return;
+                        break;
                     }
                     if (!enableRollbackForPackageSession(newRollback, childSession)) {
                         Slog.e(TAG, "Unable to enable rollback for session: " + sessionId);
-                        result.offer(-1);
-                        return;
+                        break;
                     }
                 }
             }
@@ -1188,8 +1188,7 @@
     }
 
     /**
-     * Add a rollback to the list of rollbacks. This should be called after rollback has been
-     * enabled for all packages in the rollback. It does not make the rollback available yet.
+     * Add a rollback to the list of rollbacks. It does not make the rollback available yet.
      *
      * @return the Rollback instance for a successfully enable-completed rollback,
      * or null on error.
diff --git a/services/core/java/com/android/server/tv/tuner/ClientProfile.java b/services/core/java/com/android/server/tv/tuner/ClientProfile.java
new file mode 100644
index 0000000..3845195
--- /dev/null
+++ b/services/core/java/com/android/server/tv/tuner/ClientProfile.java
@@ -0,0 +1,209 @@
+/*
+ * 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.
+ */
+
+package com.android.server.tv.tuner;
+
+/**
+  * A client profile object used by the Tuner Resource Manager to record the registered clients'
+  * information.
+  *
+  * @hide
+  */
+public final class ClientProfile {
+    public static final int INVALID_GROUP_ID = -1;
+    /**
+     * Client id sent to the client when registering with
+     * {@link #registerClientProfile(ResourceClientProfile, TunerResourceManagerCallback, int[])}
+     */
+    private final int mClientId;
+
+    /**
+     * see {@link ResourceClientProfile}
+     */
+    private final String mTvInputSessionId;
+
+    /**
+     * see {@link ResourceClientProfile}
+     */
+    private final int mUseCase;
+
+    /**
+     * Process id queried from {@link TvInputManager#}
+     */
+    private final int mProcessId;
+
+    /**
+     * All the clients that share the same resource would be under the same group id.
+     *
+     * <p>If a client's resource is to be reclaimed, all other clients under the same group id
+     * also lose their resources.
+     */
+    private int mGroupId = INVALID_GROUP_ID;
+
+    /**
+     * Optional nice value for TRM to reduce client’s priority.
+     */
+    private int mNiceValue;
+
+    /**
+     * Optional arbitrary priority value given by the client.
+     *
+     * <p>This value can override the default priorotiy calculated from
+     * the client profile.
+     */
+    private int mPriority;
+
+    private ClientProfile(ClientProfileBuilder builder) {
+        this.mClientId = builder.mClientId;
+        this.mTvInputSessionId = builder.mTvInputSessionId;
+        this.mUseCase = builder.mUseCase;
+        this.mProcessId = builder.mProcessId;
+        this.mGroupId = builder.mGroupId;
+        this.mNiceValue = builder.mNiceValue;
+        this.mPriority = builder.mPriority;
+    }
+
+    public int getClientId() {
+        return mClientId;
+    }
+
+    public String getTvInputSessionId() {
+        return mTvInputSessionId;
+    }
+
+    public int getUseCase() {
+        return mUseCase;
+    }
+
+    public int getProcessId() {
+        return mProcessId;
+    }
+
+    public int getGroupId() {
+        return mGroupId;
+    }
+
+    public int getPriority() {
+        return mPriority;
+    }
+
+    public int getNiceValue() {
+        return mNiceValue;
+    }
+
+    public void setGroupId(int groupId) {
+        mGroupId = groupId;
+    }
+
+    public void setPriority(int priority) {
+        mPriority = priority;
+    }
+
+    public void setNiceValue(int niceValue) {
+        mNiceValue = niceValue;
+    }
+
+    @Override
+    public String toString() {
+        return "ClientProfile: " + this.mClientId + ", " + this.mTvInputSessionId + ", "
+                + this.mUseCase + ", " + this.mProcessId;
+    }
+
+    public static class ClientProfileBuilder {
+        private final int mClientId;
+        private String mTvInputSessionId;
+        private int mUseCase;
+        private int mProcessId;
+        private int mGroupId;
+        private int mNiceValue;
+        private int mPriority;
+
+        ClientProfileBuilder(int clientId) {
+            this.mClientId = clientId;
+        }
+
+        /**
+          * Builder for {@link ClientProfile}.
+          *
+          * @param useCase the useCase of the client.
+          */
+        public ClientProfileBuilder useCase(int useCase) {
+            this.mUseCase = useCase;
+            return this;
+        }
+
+        /**
+          * Builder for {@link ClientProfile}.
+          *
+          * @param tvInputSessionId the id of the tv input session.
+          */
+        public ClientProfileBuilder tvInputSessionId(String tvInputSessionId) {
+            this.mTvInputSessionId = tvInputSessionId;
+            return this;
+        }
+
+        /**
+          * Builder for {@link ClientProfile}.
+          *
+          * @param processId the id of process.
+          */
+        public ClientProfileBuilder processId(int processId) {
+            this.mProcessId = processId;
+            return this;
+        }
+
+
+        /**
+          * Builder for {@link ClientProfile}.
+          *
+          * @param groupId the id of the group that shares the same resource.
+          */
+        public ClientProfileBuilder groupId(int groupId) {
+            this.mGroupId = groupId;
+            return this;
+        }
+
+        /**
+          * Builder for {@link ClientProfile}.
+          *
+          * @param niceValue the nice value of the client.
+          */
+        public ClientProfileBuilder niceValue(int niceValue) {
+            this.mNiceValue = niceValue;
+            return this;
+        }
+
+        /**
+          * Builder for {@link ClientProfile}.
+          *
+          * @param priority the priority value of the client.
+          */
+        public ClientProfileBuilder priority(int priority) {
+            this.mPriority = priority;
+            return this;
+        }
+
+        /**
+          * Build a {@link ClientProfile}.
+          *
+          * @return {@link ClientProfile}.
+          */
+        public ClientProfile build() {
+            ClientProfile clientProfile = new ClientProfile(this);
+            return clientProfile;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/tv/tuner/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tuner/TunerResourceManagerService.java
new file mode 100644
index 0000000..e876421
--- /dev/null
+++ b/services/core/java/com/android/server/tv/tuner/TunerResourceManagerService.java
@@ -0,0 +1,258 @@
+/*
+ * 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.
+ */
+
+package com.android.server.tv.tuner;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.media.tv.TvInputManager;
+import android.media.tv.tuner.CasSessionRequest;
+import android.media.tv.tuner.ITunerResourceManager;
+import android.media.tv.tuner.ITunerResourceManagerListener;
+import android.media.tv.tuner.ResourceClientProfile;
+import android.media.tv.tuner.TunerFrontendInfo;
+import android.media.tv.tuner.TunerFrontendRequest;
+import android.media.tv.tuner.TunerLnbRequest;
+import android.media.tv.tuner.TunerResourceManager;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.server.SystemService;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+  * This class provides a system service that manages the TV tuner resources.
+  *
+  * @hide
+  */
+public class TunerResourceManagerService extends SystemService {
+    private static final String TAG = "TunerResourceManagerService";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private SparseArray<ClientProfile> mClientProfiles = new SparseArray<>();
+    private SparseArray<ITunerResourceManagerListener> mListeners = new SparseArray<>();
+    private int mNextUnusedFrontendId = 0;
+    private List<Integer> mReleasedClientId = new ArrayList<Integer>();
+    private List<Integer> mAvailableFrontendIds = new ArrayList<Integer>();
+
+    private TvInputManager mManager;
+
+    public TunerResourceManagerService(@Nullable Context context) {
+        super(context);
+    }
+
+    @Override
+    public void onStart() {
+        publishBinderService(Context.TV_TUNER_RESOURCE_MGR_SERVICE, new BinderService());
+        mManager = (TvInputManager) getContext()
+                .getSystemService(Context.TV_INPUT_SERVICE);
+    }
+
+    private final class BinderService extends ITunerResourceManager.Stub {
+        @Override
+        public void registerClientProfile(@NonNull ResourceClientProfile profile,
+                            @NonNull ITunerResourceManagerListener listener,
+                            @NonNull int[] clientId) {
+            if (DEBUG) {
+                Slog.d(TAG, "registerClientProfile(clientProfile=" + profile + ")");
+            }
+
+            // TODO tell if the client already exists
+            if (mReleasedClientId.isEmpty()) {
+                clientId[0] = mNextUnusedFrontendId++;
+            } else {
+                clientId[0] = mReleasedClientId.get(0);
+                mReleasedClientId.remove(0);
+            }
+
+            if (mManager == null) {
+                Slog.e(TAG, "TvInputManager is null. Can't register client profile.");
+                return;
+            }
+
+            int callingPid = mManager.getClientPid(profile.getTvInputSessionId());
+
+            ClientProfile clientProfile = new ClientProfile.ClientProfileBuilder(
+                    clientId[0])
+                    .tvInputSessionId(profile.getTvInputSessionId())
+                    .useCase(profile.getUseCase())
+                    .processId(callingPid)
+                    .build();
+            mClientProfiles.append(clientId[0], clientProfile);
+            mListeners.append(clientId[0], listener);
+        }
+
+        @Override
+        public void unregisterClientProfile(int clientId) {
+            if (DEBUG) {
+                Slog.d(TAG, "unregisterClientProfile(clientId=" + clientId + ")");
+            }
+
+            mClientProfiles.remove(clientId);
+            mListeners.remove(clientId);
+            mReleasedClientId.add(clientId);
+        }
+
+        @Override
+        public boolean updateClientPriority(int clientId, int priority, int niceValue) {
+            if (DEBUG) {
+                Slog.d(TAG, "updateClientPriority(clientId=" + clientId
+                        + ", priority=" + priority + ", niceValue=" + niceValue + ")");
+            }
+
+            ClientProfile profile = mClientProfiles.get(clientId);
+            if (profile == null) {
+                Slog.e(TAG, "Can not find client profile with id " + clientId
+                        + " when trying to update the client priority.");
+                return false;
+            }
+
+            profile.setPriority(priority);
+            profile.setNiceValue(niceValue);
+
+            return true;
+        }
+
+        @Override
+        public void setFrontendInfoList(@NonNull TunerFrontendInfo[] infos)
+                throws RemoteException {
+            if (infos == null || infos.length == 0) {
+                Slog.d(TAG, "Can't update with empty frontend info");
+                return;
+            }
+
+            if (DEBUG) {
+                Slog.d(TAG, "updateFrontendInfo:");
+                for (int i = 0; i < infos.length; i++) {
+                    Slog.d(TAG, infos[i].toString());
+                }
+            }
+        }
+
+        @Override
+        public void updateCasInfo(int casSystemId, int maxSessionNum) {
+            if (DEBUG) {
+                Slog.d(TAG, "updateCasInfo(casSystemId="
+                        + casSystemId + ", maxSessionNum=" + maxSessionNum + ")");
+            }
+        }
+
+        @Override
+        public void setLnbInfoList(int[] lnbIds) {
+            if (DEBUG) {
+                for (int i = 0; i < lnbIds.length; i++) {
+                    Slog.d(TAG, "updateLnbInfo(lnbId=" + lnbIds[i] + ")");
+                }
+            }
+        }
+
+        @Override
+        public boolean requestFrontend(@NonNull TunerFrontendRequest request,
+                                       @NonNull int[] frontendId) throws RemoteException {
+            if (DEBUG) {
+                Slog.d(TAG, "requestFrontend(request=" + request + ")");
+            }
+
+            frontendId[0] = TunerResourceManager.INVALID_FRONTEND_ID;
+
+            if (getContext() == null) {
+                Slog.e(TAG, "Can not find context when requesting frontend");
+                return false;
+            }
+
+            if (mClientProfiles.get(request.getClientId()) == null) {
+                Slog.e(TAG, "Request from unregistered client. Id: "
+                        + request.getClientId());
+                return false;
+            }
+
+            String sessionId = mClientProfiles.get(request.getClientId())
+                    .getTvInputSessionId();
+
+            if (DEBUG) {
+                Slog.d(TAG, "session Id:" + sessionId + ")");
+            }
+
+            if (DEBUG) {
+                Slog.d(TAG, "No available Frontend found.");
+            }
+
+            return false;
+        }
+
+        @Override
+        public void shareFrontend(int selfClientId, int targetClientId) {
+            if (DEBUG) {
+                Slog.d(TAG, "shareFrontend from "
+                        + selfClientId + " with " + targetClientId);
+            }
+        }
+
+        @Override
+        public boolean requestCasSession(@NonNull CasSessionRequest request,
+                    @NonNull int[] sessionResourceId) {
+            if (DEBUG) {
+                Slog.d(TAG, "requestCasSession(request=" + request + ")");
+            }
+
+            return true;
+        }
+
+        @Override
+        public boolean requestLnb(@NonNull TunerLnbRequest request, @NonNull int[] lnbId) {
+            if (DEBUG) {
+                Slog.d(TAG, "requestLnb(request=" + request + ")");
+            }
+            return true;
+        }
+
+        @Override
+        public void releaseFrontend(int frontendId) {
+            if (DEBUG) {
+                Slog.d(TAG, "releaseFrontend(id=" + frontendId + ")");
+            }
+        }
+
+        @Override
+        public void releaseCasSession(int sessionResourceId) {
+            if (DEBUG) {
+                Slog.d(TAG, "releaseCasSession(sessionResourceId=" + sessionResourceId + ")");
+            }
+        }
+
+        @Override
+        public void releaseLnb(int lnbId) {
+            if (DEBUG) {
+                Slog.d(TAG, "releaseLnb(lnbId=" + lnbId + ")");
+            }
+        }
+
+        @Override
+        public boolean isHigherPriority(ResourceClientProfile challengerProfile,
+                ResourceClientProfile holderProfile) {
+            if (DEBUG) {
+                Slog.d(TAG, "isHigherPriority(challengerProfile=" + challengerProfile
+                        + ", holderProfile=" + challengerProfile + ")");
+            }
+            return true;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index ca6bd2d..38b0ca8 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -145,7 +145,6 @@
 import android.util.Slog;
 import android.view.DisplayCutout;
 import android.view.Gravity;
-import android.view.IApplicationToken;
 import android.view.InputChannel;
 import android.view.InputDevice;
 import android.view.InputEvent;
@@ -333,8 +332,6 @@
     private WindowState mFocusedWindow;
     private WindowState mLastFocusedWindow;
 
-    IApplicationToken mFocusedApp;
-
     // The states of decor windows from the last layout. These are used to generate another display
     // layout in different bounds but with the same states.
     private boolean mLastNavVisible;
@@ -3287,7 +3284,6 @@
                 && mLastBehavior == behavior
                 && mLastFocusIsFullscreen == isFullscreen
                 && mLastFocusIsImmersive == isImmersive
-                && mFocusedApp == win.getAppToken()
                 && mLastNonDockedStackBounds.equals(mNonDockedStackBounds)
                 && mLastDockedStackBounds.equals(mDockedStackBounds)) {
             return 0;
@@ -3304,7 +3300,6 @@
         mLastBehavior = behavior;
         mLastFocusIsFullscreen = isFullscreen;
         mLastFocusIsImmersive = isImmersive;
-        mFocusedApp = win.getAppToken();
         mLastNonDockedStackBounds.set(mNonDockedStackBounds);
         mLastDockedStackBounds.set(mDockedStackBounds);
         final Rect fullscreenStackBounds = new Rect(mNonDockedStackBounds);
@@ -3803,9 +3798,6 @@
         if (mFocusedWindow != null) {
             pw.print(prefix); pw.print("mFocusedWindow="); pw.println(mFocusedWindow);
         }
-        if (mFocusedApp != null) {
-            pw.print(prefix); pw.print("mFocusedApp="); pw.println(mFocusedApp);
-        }
         if (mTopFullscreenOpaqueWindowState != null) {
             pw.print(prefix); pw.print("mTopFullscreenOpaqueWindowState=");
             pw.println(mTopFullscreenOpaqueWindowState);
diff --git a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
index 2f61ca0..63346b9 100644
--- a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
+++ b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
@@ -67,7 +67,7 @@
 
         final PooledConsumer c = PooledLambda.obtainConsumer(
                 ResetTargetTaskHelper::processTask, this, PooledLambda.__(Task.class));
-        targetTask.mWmService.mRoot.forAllTasks(c);
+        targetTask.mWmService.mRoot.forAllTasks(c, true /*traverseTopToBottom*/, mTargetStack);
         c.recycle();
 
         processPendingReparentActivities();
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 9c23ed3..0aed691 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3070,17 +3070,11 @@
         return matchParentBounds();
     }
 
+    @Override
     void forAllTasks(Consumer<Task> callback, boolean traverseTopToBottom, Task excludedTask) {
-        if (traverseTopToBottom) {
-            super.forAllTasks(callback, traverseTopToBottom);
-            if (excludedTask != this) {
-                callback.accept(this);
-            }
-        } else {
-            super.forAllTasks(callback, traverseTopToBottom);
-            if (excludedTask != this) {
-                callback.accept(this);
-            }
+        super.forAllTasks(callback, traverseTopToBottom, excludedTask);
+        if (excludedTask != this) {
+            callback.accept(this);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 504aa2d..0bb4e03 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -1425,6 +1425,19 @@
         }
     }
 
+    void forAllTasks(Consumer<Task> callback, boolean traverseTopToBottom, Task excludedTask) {
+        final int count = mChildren.size();
+        if (traverseTopToBottom) {
+            for (int i = count - 1; i >= 0; --i) {
+                mChildren.get(i).forAllTasks(callback, traverseTopToBottom, excludedTask);
+            }
+        } else {
+            for (int i = 0; i < count; i++) {
+                mChildren.get(i).forAllTasks(callback, traverseTopToBottom, excludedTask);
+            }
+        }
+    }
+
     Task getTaskAbove(Task t) {
         return getTask(
                 (above) -> true, t, false /*includeBoundary*/, false /*traverseTopToBottom*/);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index d2ec436..aa5dafc 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -57,6 +57,7 @@
 import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME;
 import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS;
 import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
+import static android.app.admin.DevicePolicyManager.NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
@@ -522,7 +523,8 @@
 
     /** Keyguard features that are allowed to be set on a managed profile */
     private static final int PROFILE_KEYGUARD_FEATURES =
-            PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER | PROFILE_KEYGUARD_FEATURES_PROFILE_ONLY;
+            NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER
+                    | PROFILE_KEYGUARD_FEATURES_PROFILE_ONLY;
 
     private static final int DEVICE_ADMIN_DEACTIVATE_TIMEOUT = 10000;
 
@@ -2542,7 +2544,7 @@
      * corporate owned device.
      */
     @GuardedBy("getLockObject()")
-    private void maybeMigrateToProfileOnOrganizationOwnedDeviceLocked() {
+    private void migrateToProfileOnOrganizationOwnedDeviceIfCompLocked() {
         logIfVerbose("Checking whether we need to migrate COMP ");
         final int doUserId = mOwners.getDeviceOwnerUserId();
         if (doUserId == UserHandle.USER_NULL) {
@@ -2605,6 +2607,11 @@
 
         // Note: KeyChain keys are not removed and will remain accessible for the apps that have
         // been given grants to use them.
+
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.COMP_TO_ORG_OWNED_PO_MIGRATED)
+                .setAdmin(poAdminComponent)
+                .write();
     }
 
     private void moveDoPoliciesToProfileParentAdmin(ActiveAdmin doAdmin, ActiveAdmin parentAdmin) {
@@ -3865,7 +3872,7 @@
             case SystemService.PHASE_ACTIVITY_MANAGER_READY:
                 maybeStartSecurityLogMonitorOnActivityManagerReady();
                 synchronized (getLockObject()) {
-                    maybeMigrateToProfileOnOrganizationOwnedDeviceLocked();
+                    migrateToProfileOnOrganizationOwnedDeviceIfCompLocked();
                 }
                 final int userId = getManagedUserId(UserHandle.USER_SYSTEM);
                 if (userId >= 0) {
@@ -8163,16 +8170,20 @@
         }
         Objects.requireNonNull(who, "ComponentName is null");
         final int userHandle = mInjector.userHandleGetCallingUserId();
-        if (isManagedProfile(userHandle)) {
-            if (parent) {
-                which = which & PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
-            } else {
-                which = which & PROFILE_KEYGUARD_FEATURES;
-            }
-        }
         synchronized (getLockObject()) {
             ActiveAdmin ap = getActiveAdminForCallerLocked(
                     who, DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES, parent);
+            if (isManagedProfile(userHandle)) {
+                if (parent) {
+                    if (isProfileOwnerOfOrganizationOwnedDevice(ap)) {
+                        which = which & PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
+                    } else {
+                        which = which & NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
+                    }
+                } else {
+                    which = which & PROFILE_KEYGUARD_FEATURES;
+                }
+            }
             if (ap.disabledKeyguardFeatures != which) {
                 ap.disabledKeyguardFeatures = which;
                 saveSettingsLocked(userHandle);
@@ -11553,7 +11564,7 @@
     }
 
     @Override
-    public void setLockdownAdminConfiguredNetworks(ComponentName who, boolean lockdown) {
+    public void setConfiguredNetworksLockdownState(ComponentName who, boolean lockdown) {
         if (!mHasFeature) {
             return;
         }
@@ -11572,7 +11583,7 @@
     }
 
     @Override
-    public boolean isLockdownAdminConfiguredNetworks(ComponentName who) {
+    public boolean hasLockdownAdminConfiguredNetworks(ComponentName who) {
         if (!mHasFeature) {
             return false;
         }
@@ -15551,6 +15562,12 @@
 
         mInjector.binderWithCleanCallingIdentity(
                 () -> applyPersonalAppsSuspension(callingUserId, suspended));
+
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_PERSONAL_APPS_SUSPENDED)
+                .setAdmin(who)
+                .setBoolean(suspended)
+                .write();
     }
 
     /**
@@ -15722,9 +15739,16 @@
 
         mInjector.binderWithCleanCallingIdentity(
                 () -> updatePersonalAppSuspension(userId, mUserManager.isUserUnlocked()));
+
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_MANAGED_PROFILE_MAXIMUM_TIME_OFF)
+                .setAdmin(who)
+                .setTimePeriod(timeoutMs)
+                .write();
     }
 
-    void enforceHandlesCheckPolicyComplianceIntent(@UserIdInt int userId, String packageName) {
+    private void enforceHandlesCheckPolicyComplianceIntent(
+            @UserIdInt int userId, String packageName) {
         mInjector.binderWithCleanCallingIdentity(() -> {
             final Intent intent = new Intent(DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE);
             intent.setPackage(packageName);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index eef6c63..93662c9 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -162,6 +162,7 @@
 import com.android.server.trust.TrustManagerService;
 import com.android.server.tv.TvInputManagerService;
 import com.android.server.tv.TvRemoteService;
+import com.android.server.tv.tuner.TunerResourceManagerService;
 import com.android.server.twilight.TwilightService;
 import com.android.server.uri.UriGrantsManagerService;
 import com.android.server.usage.UsageStatsService;
@@ -1854,6 +1855,8 @@
                     || mPackageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
                 t.traceBegin("StartTvInputManager");
                 mSystemServiceManager.startService(TvInputManagerService.class);
+                t.traceBegin("StartTunerResourceManager");
+                mSystemServiceManager.startService(TunerResourceManagerService.class);
                 t.traceEnd();
             }
 
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index c8673f8..a904b42 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -322,7 +322,8 @@
     private void updateDefaultDialer(@NonNull UserData userData) {
         TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
         String defaultDialer = telecomManager != null
-                ? telecomManager.getDefaultDialerPackage(userData.getUserId()) : null;
+                ? telecomManager.getDefaultDialerPackage(
+                        new UserHandle(userData.getUserId())) : null;
         userData.setDefaultDialer(defaultDialer);
     }
 
diff --git a/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java b/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java
index 4e37f47..644c155 100644
--- a/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java
+++ b/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java
@@ -59,7 +59,7 @@
      */
     boolean querySince(long sinceTime) {
         UsageEvents usageEvents = mUsageStatsManagerInternal.queryEventsForUser(
-                mUserId, sinceTime, System.currentTimeMillis(), false, false);
+                mUserId, sinceTime, System.currentTimeMillis(), false, false, false);
         if (usageEvents == null) {
             return false;
         }
diff --git a/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/DummyAppTwoCerts.apk b/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/DummyAppTwoCerts.apk
new file mode 100644
index 0000000..9161d86
--- /dev/null
+++ b/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/DummyAppTwoCerts.apk
Binary files differ
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 8a7462c..164ee31 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -1095,6 +1095,24 @@
     }
 
     @Test
+    public void testRegisterAuthenticator_updatesStrengths() throws Exception {
+        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService.onStart();
+
+        verify(mBiometricService.mBiometricStrengthController).startListening();
+        verify(mBiometricService.mBiometricStrengthController, never()).updateStrengths();
+
+        when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any()))
+                .thenReturn(true);
+        when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
+        mBiometricService.mImpl.registerAuthenticator(0 /* testId */,
+                BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+                mFingerprintAuthenticator);
+
+        verify(mBiometricService.mBiometricStrengthController).updateStrengths();
+    }
+
+    @Test
     public void testWithDowngradedAuthenticator() throws Exception {
         mBiometricService = new BiometricService(mContext, mInjector);
         mBiometricService.onStart();
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 3f74681..8f70cca 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -2188,6 +2188,42 @@
         assertThat(actualAccounts).containsExactlyElementsIn(expectedAccounts);
     }
 
+    public void testSetKeyguardDisabledFeaturesWithDO() throws Exception {
+        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+        setupDeviceOwner();
+
+        dpm.setKeyguardDisabledFeatures(admin1, DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA);
+
+        assertThat(dpm.getKeyguardDisabledFeatures(admin1)).isEqualTo(
+                DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA);
+    }
+
+    public void testSetKeyguardDisabledFeaturesWithPO() throws Exception {
+        setupProfileOwner();
+
+        dpm.setKeyguardDisabledFeatures(admin1, DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT);
+
+        assertThat(dpm.getKeyguardDisabledFeatures(admin1)).isEqualTo(
+                DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT);
+    }
+
+    public void testSetKeyguardDisabledFeaturesWithPOOfOrganizationOwnedDevice()
+            throws Exception {
+        final int MANAGED_PROFILE_USER_ID = DpmMockContext.CALLER_USER_HANDLE;
+        final int MANAGED_PROFILE_ADMIN_UID =
+                UserHandle.getUid(MANAGED_PROFILE_USER_ID, DpmMockContext.SYSTEM_UID);
+        mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
+
+        addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
+        configureProfileOwnerOfOrgOwnedDevice(admin1, DpmMockContext.CALLER_USER_HANDLE);
+
+        parentDpm.setKeyguardDisabledFeatures(admin1,
+                DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA);
+
+        assertThat(parentDpm.getKeyguardDisabledFeatures(admin1)).isEqualTo(
+                DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA);
+    }
+
     public void testSetApplicationHiddenWithDO() throws Exception {
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
@@ -3781,35 +3817,35 @@
         assertEquals(-1, dpm.getLastSecurityLogRetrievalTime());
     }
 
-    public void testSetLockdownAdminConfiguredNetworksWithDO() throws Exception {
+    public void testSetConfiguredNetworksLockdownStateWithDO() throws Exception {
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
-        dpm.setLockdownAdminConfiguredNetworks(admin1, true);
+        dpm.setConfiguredNetworksLockdownState(admin1, true);
         verify(getServices().settings).settingsGlobalPutInt(
                 Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 1);
 
-        dpm.setLockdownAdminConfiguredNetworks(admin1, false);
+        dpm.setConfiguredNetworksLockdownState(admin1, false);
         verify(getServices().settings).settingsGlobalPutInt(
                 Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0);
     }
 
-    public void testSetLockdownAdminConfiguredNetworksWithPO() throws Exception {
+    public void testSetConfiguredNetworksLockdownStateWithPO() throws Exception {
         setupProfileOwner();
         assertExpectException(SecurityException.class, null,
-                () -> dpm.setLockdownAdminConfiguredNetworks(admin1, false));
+                () -> dpm.setConfiguredNetworksLockdownState(admin1, false));
         verify(getServices().settings, never()).settingsGlobalPutInt(
                 Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0);
     }
 
-    public void testSetLockdownAdminConfiguredNetworksWithPOOfOrganizationOwnedDevice()
+    public void testSetConfiguredNetworksLockdownStateWithPOOfOrganizationOwnedDevice()
             throws Exception {
         setupProfileOwner();
         configureProfileOwnerOfOrgOwnedDevice(admin1, DpmMockContext.CALLER_USER_HANDLE);
-        dpm.setLockdownAdminConfiguredNetworks(admin1, true);
+        dpm.setConfiguredNetworksLockdownState(admin1, true);
         verify(getServices().settings).settingsGlobalPutInt(
                 Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 1);
 
-        dpm.setLockdownAdminConfiguredNetworks(admin1, false);
+        dpm.setConfiguredNetworksLockdownState(admin1, false);
         verify(getServices().settings).settingsGlobalPutInt(
                 Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0);
     }
diff --git a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
index 8dae48c..0d4c6e8 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
@@ -19,6 +19,7 @@
 import static android.content.integrity.AppIntegrityManager.EXTRA_STATUS;
 import static android.content.integrity.AppIntegrityManager.STATUS_FAILURE;
 import static android.content.integrity.AppIntegrityManager.STATUS_SUCCESS;
+import static android.content.integrity.InstallerAllowedByManifestFormula.INSTALLER_CERTIFICATE_NOT_EVALUATED;
 import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID;
 import static android.content.pm.PackageManager.EXTRA_VERIFICATION_INSTALLER_PACKAGE;
 import static android.content.pm.PackageManager.EXTRA_VERIFICATION_INSTALLER_UID;
@@ -39,6 +40,8 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
+
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -65,6 +68,7 @@
 import com.android.server.integrity.model.IntegrityCheckResult;
 import com.android.server.testutils.TestUtils;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -76,8 +80,9 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -87,6 +92,9 @@
     private static final String TEST_APP_PATH =
             "/data/local/tmp/AppIntegrityManagerServiceTestApp.apk";
 
+    private static final String TEST_APP_TWO_CERT_PATH =
+            "AppIntegrityManagerServiceImplTest/DummyAppTwoCerts.apk";
+
     private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive";
     private static final String VERSION = "version";
     private static final String TEST_FRAMEWORK_PACKAGE = "com.android.frameworks.servicestests";
@@ -105,6 +113,11 @@
     private static final String INSTALLER_SHA256 =
             "30F41A7CBF96EE736A54DD6DF759B50ED3CC126ABCEF694E167C324F5976C227";
 
+    private static final String DUMMY_APP_TWO_CERTS_CERT_1 =
+            "C0369C2A1096632429DFA8433068AECEAD00BAC337CA92A175036D39CC9AFE94";
+    private static final String DUMMY_APP_TWO_CERTS_CERT_2 =
+            "94366E0A80F3A3F0D8171A15760B88E228CD6E1101F0414C98878724FBE70147";
+
     private static final String PLAY_STORE_PKG = "com.android.vending";
     private static final String ADB_INSTALLER = "adb";
     private static final String PLAY_STORE_CERT = "play_store_cert";
@@ -128,6 +141,7 @@
 
     private PackageManager mSpyPackageManager;
     private File mTestApk;
+    private File mTestApkTwoCerts;
 
     private final Context mRealContext = InstrumentationRegistry.getTargetContext();
     // under test
@@ -136,6 +150,10 @@
     @Before
     public void setup() throws Exception {
         mTestApk = new File(TEST_APP_PATH);
+        mTestApkTwoCerts = File.createTempFile("AppIntegrity", ".apk");
+        try (InputStream inputStream = mRealContext.getAssets().open(TEST_APP_TWO_CERT_PATH)) {
+            Files.copy(inputStream, mTestApkTwoCerts.toPath(), REPLACE_EXISTING);
+        }
 
         mService =
                 new AppIntegrityManagerServiceImpl(
@@ -154,6 +172,11 @@
         when(mIntegrityFileManager.initialized()).thenReturn(true);
     }
 
+    @After
+    public void tearDown() throws Exception {
+        mTestApkTwoCerts.delete();
+    }
+
     @Test
     public void updateRuleSet_notAuthorized() throws Exception {
         makeUsSystemApp();
@@ -268,20 +291,16 @@
         verify(mMockContext)
                 .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
         Intent intent = makeVerificationIntent();
-        when(mRuleEvaluationEngine.evaluate(any(), any())).thenReturn(IntegrityCheckResult.allow());
+        when(mRuleEvaluationEngine.evaluate(any())).thenReturn(IntegrityCheckResult.allow());
 
         broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
         runJobInHandler();
 
         ArgumentCaptor<AppInstallMetadata> metadataCaptor =
                 ArgumentCaptor.forClass(AppInstallMetadata.class);
-        Map<String, String> allowedInstallers = new HashMap<>();
-        ArgumentCaptor<Map<String, String>> allowedInstallersCaptor =
-                ArgumentCaptor.forClass(allowedInstallers.getClass());
         verify(mRuleEvaluationEngine)
-                .evaluate(metadataCaptor.capture(), allowedInstallersCaptor.capture());
+                .evaluate(metadataCaptor.capture());
         AppInstallMetadata appInstallMetadata = metadataCaptor.getValue();
-        allowedInstallers = allowedInstallersCaptor.getValue();
         assertEquals(PACKAGE_NAME, appInstallMetadata.getPackageName());
         assertThat(appInstallMetadata.getAppCertificates()).containsExactly(APP_CERT);
         assertEquals(INSTALLER_SHA256, appInstallMetadata.getInstallerName());
@@ -289,9 +308,34 @@
         assertEquals(VERSION_CODE, appInstallMetadata.getVersionCode());
         assertFalse(appInstallMetadata.isPreInstalled());
         // These are hardcoded in the test apk android manifest
+        Map<String, String> allowedInstallers =
+                appInstallMetadata.getAllowedInstallersAndCertificates();
         assertEquals(2, allowedInstallers.size());
         assertEquals(PLAY_STORE_CERT, allowedInstallers.get(PLAY_STORE_PKG));
-        assertEquals(ADB_CERT, allowedInstallers.get(ADB_INSTALLER));
+        assertEquals(INSTALLER_CERTIFICATE_NOT_EVALUATED, allowedInstallers.get(ADB_INSTALLER));
+    }
+
+    @Test
+    public void handleBroadcast_correctArgs_multipleCerts() throws Exception {
+        whitelistUsAsRuleProvider();
+        makeUsSystemApp();
+        ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        verify(mMockContext)
+                .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
+        Intent intent = makeVerificationIntent();
+        intent.setDataAndType(Uri.fromFile(mTestApkTwoCerts), PACKAGE_MIME_TYPE);
+        when(mRuleEvaluationEngine.evaluate(any())).thenReturn(IntegrityCheckResult.allow());
+
+        broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
+        runJobInHandler();
+
+        ArgumentCaptor<AppInstallMetadata> metadataCaptor =
+                ArgumentCaptor.forClass(AppInstallMetadata.class);
+        verify(mRuleEvaluationEngine).evaluate(metadataCaptor.capture());
+        AppInstallMetadata appInstallMetadata = metadataCaptor.getValue();
+        assertThat(appInstallMetadata.getAppCertificates()).containsExactly(
+                DUMMY_APP_TWO_CERTS_CERT_1, DUMMY_APP_TWO_CERTS_CERT_2);
     }
 
     @Test
@@ -303,7 +347,7 @@
         verify(mMockContext)
                 .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
         Intent intent = makeVerificationIntent();
-        when(mRuleEvaluationEngine.evaluate(any(), any())).thenReturn(IntegrityCheckResult.allow());
+        when(mRuleEvaluationEngine.evaluate(any())).thenReturn(IntegrityCheckResult.allow());
 
         broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
         runJobInHandler();
@@ -321,7 +365,7 @@
                 ArgumentCaptor.forClass(BroadcastReceiver.class);
         verify(mMockContext)
                 .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
-        when(mRuleEvaluationEngine.evaluate(any(), any()))
+        when(mRuleEvaluationEngine.evaluate(any()))
                 .thenReturn(
                         IntegrityCheckResult.deny(
                                 Arrays.asList(
@@ -349,7 +393,7 @@
         verify(mMockContext)
                 .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
         Intent intent = makeVerificationIntent();
-        when(mRuleEvaluationEngine.evaluate(any(), any())).thenReturn(IntegrityCheckResult.allow());
+        when(mRuleEvaluationEngine.evaluate(any())).thenReturn(IntegrityCheckResult.allow());
 
         broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
         runJobInHandler();
@@ -377,7 +421,7 @@
         verify(mMockContext, atLeastOnce())
                 .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
         Intent intent = makeVerificationIntent(TEST_FRAMEWORK_PACKAGE);
-        when(mRuleEvaluationEngine.evaluate(any(), any()))
+        when(mRuleEvaluationEngine.evaluate(any()))
                 .thenReturn(IntegrityCheckResult.deny(/* rule= */ null));
 
         broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
diff --git a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java
index b0b9596..0488745 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java
@@ -22,6 +22,8 @@
 import static org.mockito.Mockito.when;
 
 import android.content.integrity.AppInstallMetadata;
+import android.content.integrity.IntegrityFormula;
+import android.content.integrity.Rule;
 
 import com.android.server.integrity.IntegrityFileManager;
 import com.android.server.integrity.model.IntegrityCheckResult;
@@ -33,7 +35,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
@@ -60,13 +61,14 @@
 
         mEngine = new RuleEvaluationEngine(mIntegrityFileManager);
 
-        when(mIntegrityFileManager.readRules(any())).thenReturn(new ArrayList<>());
+        when(mIntegrityFileManager.readRules(any())).thenReturn(Collections.singletonList(new Rule(
+                IntegrityFormula.Installer.notAllowedByManifest(), Rule.DENY)));
+
+        when(mIntegrityFileManager.initialized()).thenReturn(true);
     }
 
     @Test
     public void testAllowedInstallers_empty() {
-        Map<String, String> allowedInstallers = Collections.emptyMap();
-
         AppInstallMetadata appInstallMetadata1 =
                 getAppInstallMetadataBuilder()
                         .setInstallerName(INSTALLER_1)
@@ -83,11 +85,11 @@
                         .setInstallerCertificates(Collections.singletonList(RANDOM_INSTALLER_CERT))
                         .build();
 
-        assertThat(mEngine.evaluate(appInstallMetadata1, allowedInstallers).getEffect())
+        assertThat(mEngine.evaluate(appInstallMetadata1).getEffect())
                 .isEqualTo(IntegrityCheckResult.Effect.ALLOW);
-        assertThat(mEngine.evaluate(appInstallMetadata2, allowedInstallers).getEffect())
+        assertThat(mEngine.evaluate(appInstallMetadata2).getEffect())
                 .isEqualTo(IntegrityCheckResult.Effect.ALLOW);
-        assertThat(mEngine.evaluate(appInstallMetadata3, allowedInstallers).getEffect())
+        assertThat(mEngine.evaluate(appInstallMetadata3).getEffect())
                 .isEqualTo(IntegrityCheckResult.Effect.ALLOW);
     }
 
@@ -100,32 +102,36 @@
                 getAppInstallMetadataBuilder()
                         .setInstallerName(INSTALLER_1)
                         .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
+                        .setAllowedInstallersAndCert(allowedInstallers)
                         .build();
-        assertThat(mEngine.evaluate(appInstallMetadata1, allowedInstallers).getEffect())
+        assertThat(mEngine.evaluate(appInstallMetadata1).getEffect())
                 .isEqualTo(IntegrityCheckResult.Effect.ALLOW);
 
         AppInstallMetadata appInstallMetadata2 =
                 getAppInstallMetadataBuilder()
                         .setInstallerName(RANDOM_INSTALLER)
+                        .setAllowedInstallersAndCert(allowedInstallers)
                         .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
                         .build();
-        assertThat(mEngine.evaluate(appInstallMetadata2, allowedInstallers).getEffect())
+        assertThat(mEngine.evaluate(appInstallMetadata2).getEffect())
                 .isEqualTo(IntegrityCheckResult.Effect.DENY);
 
         AppInstallMetadata appInstallMetadata3 =
                 getAppInstallMetadataBuilder()
                         .setInstallerName(INSTALLER_1)
+                        .setAllowedInstallersAndCert(allowedInstallers)
                         .setInstallerCertificates(Collections.singletonList(RANDOM_INSTALLER_CERT))
                         .build();
-        assertThat(mEngine.evaluate(appInstallMetadata3, allowedInstallers).getEffect())
+        assertThat(mEngine.evaluate(appInstallMetadata3).getEffect())
                 .isEqualTo(IntegrityCheckResult.Effect.DENY);
 
         AppInstallMetadata appInstallMetadata4 =
                 getAppInstallMetadataBuilder()
                         .setInstallerName(INSTALLER_1)
+                        .setAllowedInstallersAndCert(allowedInstallers)
                         .setInstallerCertificates(Collections.singletonList(RANDOM_INSTALLER_CERT))
                         .build();
-        assertThat(mEngine.evaluate(appInstallMetadata4, allowedInstallers).getEffect())
+        assertThat(mEngine.evaluate(appInstallMetadata4).getEffect())
                 .isEqualTo(IntegrityCheckResult.Effect.DENY);
     }
 
@@ -138,57 +144,37 @@
         AppInstallMetadata appInstallMetadata1 =
                 getAppInstallMetadataBuilder()
                         .setInstallerName(INSTALLER_1)
+                        .setAllowedInstallersAndCert(allowedInstallers)
                         .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
                         .build();
-        assertThat(mEngine.evaluate(appInstallMetadata1, allowedInstallers).getEffect())
+        assertThat(mEngine.evaluate(appInstallMetadata1).getEffect())
                 .isEqualTo(IntegrityCheckResult.Effect.ALLOW);
 
         AppInstallMetadata appInstallMetadata2 =
                 getAppInstallMetadataBuilder()
                         .setInstallerName(INSTALLER_2)
+                        .setAllowedInstallersAndCert(allowedInstallers)
                         .setInstallerCertificates(Collections.singletonList(INSTALLER_2_CERT))
                         .build();
-        assertThat(mEngine.evaluate(appInstallMetadata2, allowedInstallers).getEffect())
+        assertThat(mEngine.evaluate(appInstallMetadata2).getEffect())
                 .isEqualTo(IntegrityCheckResult.Effect.ALLOW);
 
         AppInstallMetadata appInstallMetadata3 =
                 getAppInstallMetadataBuilder()
                         .setInstallerName(INSTALLER_1)
+                        .setAllowedInstallersAndCert(allowedInstallers)
                         .setInstallerCertificates(Collections.singletonList(INSTALLER_2_CERT))
                         .build();
-        assertThat(mEngine.evaluate(appInstallMetadata3, allowedInstallers).getEffect())
+        assertThat(mEngine.evaluate(appInstallMetadata3).getEffect())
                 .isEqualTo(IntegrityCheckResult.Effect.DENY);
 
         AppInstallMetadata appInstallMetadata4 =
                 getAppInstallMetadataBuilder()
                         .setInstallerName(INSTALLER_2)
+                        .setAllowedInstallersAndCert(allowedInstallers)
                         .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
                         .build();
-        assertThat(mEngine.evaluate(appInstallMetadata4, allowedInstallers).getEffect())
-                .isEqualTo(IntegrityCheckResult.Effect.DENY);
-    }
-
-    @Test
-    public void manifestBasedRuleEvaluationWorksEvenWhenIntegrityFilesAreUnavailable() {
-        when(mIntegrityFileManager.initialized()).thenReturn(false);
-
-        Map<String, String> allowedInstallers =
-                Collections.singletonMap(INSTALLER_1, INSTALLER_1_CERT);
-
-        AppInstallMetadata appInstallMetadata1 =
-                getAppInstallMetadataBuilder()
-                        .setInstallerName(INSTALLER_1)
-                        .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
-                        .build();
-        assertThat(mEngine.evaluate(appInstallMetadata1, allowedInstallers).getEffect())
-                .isEqualTo(IntegrityCheckResult.Effect.ALLOW);
-
-        AppInstallMetadata appInstallMetadata2 =
-                getAppInstallMetadataBuilder()
-                        .setInstallerName(RANDOM_INSTALLER)
-                        .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
-                        .build();
-        assertThat(mEngine.evaluate(appInstallMetadata2, allowedInstallers).getEffect())
+        assertThat(mEngine.evaluate(appInstallMetadata4).getEffect())
                 .isEqualTo(IntegrityCheckResult.Effect.DENY);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
index 498d888..6769faa 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
@@ -142,7 +142,8 @@
         when(mContext.getSystemService(Context.TELECOM_SERVICE)).thenReturn(mTelecomManager);
         when(mContext.getSystemServiceName(TelecomManager.class)).thenReturn(
                 Context.TELECOM_SERVICE);
-        when(mTelecomManager.getDefaultDialerPackage(anyInt())).thenReturn(TEST_PKG_NAME);
+        when(mTelecomManager.getDefaultDialerPackage(any(UserHandle.class)))
+                .thenReturn(TEST_PKG_NAME);
 
         when(mExecutorService.scheduleAtFixedRate(any(Runnable.class), anyLong(), anyLong(), any(
                 TimeUnit.class))).thenReturn(mScheduledFuture);
diff --git a/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java b/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java
index b273578..01d9dc0 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java
@@ -189,7 +189,7 @@
     private void addUsageEvents(UsageEvents.Event... events) {
         UsageEvents usageEvents = new UsageEvents(Arrays.asList(events), new String[]{});
         when(mUsageStatsManagerInternal.queryEventsForUser(anyInt(), anyLong(), anyLong(),
-                anyBoolean(), anyBoolean())).thenReturn(usageEvents);
+                anyBoolean(), anyBoolean(), anyBoolean())).thenReturn(usageEvents);
     }
 
     private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index e768205..9e57763 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -145,6 +145,7 @@
     static class MyInjector extends AppStandbyController.Injector {
         long mElapsedRealtime;
         boolean mIsAppIdleEnabled = true;
+        boolean mIsCharging;
         List<String> mPowerSaveWhitelistExceptIdle = new ArrayList<>();
         boolean mDisplayOn;
         DisplayManager.DisplayListener mDisplayListener;
@@ -179,6 +180,11 @@
         }
 
         @Override
+        boolean isCharging() {
+            return mIsCharging;
+        }
+
+        @Override
         boolean isPowerSaveWhitelistExceptIdleApp(String packageName) throws RemoteException {
             return mPowerSaveWhitelistExceptIdle.contains(packageName);
         }
@@ -281,6 +287,13 @@
         } catch (PackageManager.NameNotFoundException nnfe) {}
     }
 
+    private void setChargingState(AppStandbyController controller, boolean charging) {
+        mInjector.mIsCharging = charging;
+        if (controller != null) {
+            controller.setChargingState(charging);
+        }
+    }
+
     private void setAppIdleEnabled(AppStandbyController controller, boolean enabled) {
         mInjector.mIsAppIdleEnabled = enabled;
         if (controller != null) {
@@ -297,6 +310,7 @@
         controller.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
         mInjector.setDisplayOn(false);
         mInjector.setDisplayOn(true);
+        setChargingState(controller, false);
         controller.checkIdleStates(USER_ID);
         assertNotEquals(STANDBY_BUCKET_EXEMPTED,
                 controller.getAppStandbyBucket(PACKAGE_1, USER_ID,
@@ -324,6 +338,46 @@
                         mInjector.mElapsedRealtime, false));
     }
 
+    @Test
+    public void testIsAppIdle_Charging() throws Exception {
+        setChargingState(mController, false);
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
+                REASON_MAIN_FORCED_BY_SYSTEM);
+        assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+        assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
+        assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
+
+        setChargingState(mController, true);
+        assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+        assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
+        assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
+
+        setChargingState(mController, false);
+        assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+        assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
+        assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
+    }
+
+    @Test
+    public void testIsAppIdle_Enabled() throws Exception {
+        setChargingState(mController, false);
+        setAppIdleEnabled(mController, true);
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
+                REASON_MAIN_FORCED_BY_SYSTEM);
+        assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+        assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
+        assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
+
+        setAppIdleEnabled(mController, false);
+        assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
+        assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
+
+        setAppIdleEnabled(mController, true);
+        assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+        assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
+        assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
+    }
+
     private void assertTimeout(AppStandbyController controller, long elapsedTime, int bucket) {
         mInjector.mElapsedRealtime = elapsedTime;
         controller.checkIdleStates(USER_ID);
diff --git a/services/tests/servicestests/test-apps/AppIntegrityManagerServiceTestApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/AppIntegrityManagerServiceTestApp/AndroidManifest.xml
index f5dbf43..98572d4 100644
--- a/services/tests/servicestests/test-apps/AppIntegrityManagerServiceTestApp/AndroidManifest.xml
+++ b/services/tests/servicestests/test-apps/AppIntegrityManagerServiceTestApp/AndroidManifest.xml
@@ -22,7 +22,7 @@
     <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="28" />
 
     <application android:hasCode="false">
-        <meta-data android:name="allowed-installers" android:value="com.android.vending|play_store_cert,adb|"/>
+        <meta-data android:name="allowed-installers" android:value="com.android.vending|play_store_cert,adb"/>
     </application>
 </manifest>
 
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 a0f7f5b..75efdd7 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -5373,21 +5373,7 @@
     }
 
     @Test
-    public void testCancelAllNotifications_cancelsBubble() throws Exception {
-        final NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
-        nr.getSbn().getNotification().flags |= FLAG_BUBBLE;
-        mService.addNotification(nr);
-
-        mBinderService.cancelAllNotifications(PKG, nr.getSbn().getUserId());
-        waitForIdle();
-
-        StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
-        assertEquals(0, notifs.length);
-        assertEquals(0, mService.getNotificationRecordCount());
-    }
-
-    @Test
-    public void testAppCancelNotifications_cancelsBubbles() throws Exception {
+    public void testCancelNotificationsFromApp_cancelsBubbles() throws Exception {
         final NotificationRecord nrBubble = generateNotificationRecord(mTestNotificationChannel);
         nrBubble.getSbn().getNotification().flags |= FLAG_BUBBLE;
 
@@ -5413,6 +5399,20 @@
     }
 
     @Test
+    public void testCancelAllNotificationsFromApp_cancelsBubble() throws Exception {
+        final NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
+        nr.getSbn().getNotification().flags |= FLAG_BUBBLE;
+        mService.addNotification(nr);
+
+        mBinderService.cancelAllNotifications(PKG, nr.getSbn().getUserId());
+        waitForIdle();
+
+        StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
+        assertEquals(0, notifs.length);
+        assertEquals(0, mService.getNotificationRecordCount());
+    }
+
+    @Test
     public void testCancelAllNotificationsFromListener_ignoresBubbles() throws Exception {
         final NotificationRecord nrNormal = generateNotificationRecord(mTestNotificationChannel);
         final NotificationRecord nrBubble = generateNotificationRecord(mTestNotificationChannel);
@@ -5448,6 +5448,25 @@
     }
 
     @Test
+    public void testCancelAllNotificationsFromStatusBar_ignoresBubble() throws Exception {
+        // GIVEN a notification bubble
+        final NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
+        nr.getSbn().getNotification().flags |= FLAG_BUBBLE;
+        mService.addNotification(nr);
+
+        // WHEN the status bar clears all notifications
+        mService.mNotificationDelegate.onClearAll(mUid, Binder.getCallingPid(),
+                nr.getSbn().getUserId());
+        waitForIdle();
+
+        // THEN the bubble notification does not get removed
+        StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
+        assertEquals(1, notifs.length);
+        assertEquals(1, mService.getNotificationRecordCount());
+    }
+
+
+    @Test
     public void testGetAllowedAssistantAdjustments() throws Exception {
         List<String> capabilities = mBinderService.getAllowedAssistantAdjustments(null);
         assertNotNull(capabilities);
@@ -6105,6 +6124,26 @@
     }
 
     @Test
+    public void testNotificationBubbles_bubbleStays_whenClicked()
+            throws Exception {
+        // GIVEN a notification that has the auto cancels flag (cancel on click) and is a bubble
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
+        final NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
+        nr.getSbn().getNotification().flags |= FLAG_BUBBLE | FLAG_AUTO_CANCEL;
+        mService.addNotification(nr);
+
+        // WHEN we click the notification
+        final NotificationVisibility nv = NotificationVisibility.obtain(nr.getKey(), 1, 2, true);
+        mService.mNotificationDelegate.onNotificationClick(mUid, Binder.getCallingPid(),
+                nr.getKey(), nv);
+        waitForIdle();
+
+        // THEN the bubble should still exist
+        StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG);
+        assertEquals(1, notifsAfter.length);
+    }
+
+    @Test
     public void testLoadDefaultApprovedServices_emptyResources() {
         TestableResources tr = mContext.getOrCreateTestableResources();
         tr.addOverride(com.android.internal.R.string.config_defaultListenerAccessPackages, "");
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 1371481..420695d 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -491,6 +491,15 @@
         return true; // hide by default if we can't verify visibility
     }
 
+    private boolean shouldHideLocusIdEvents(int callingPid, int callingUid) {
+        if (callingUid == Process.SYSTEM_UID) {
+            return false;
+        }
+        return !(getContext().checkPermission(
+                android.Manifest.permission.ACCESS_LOCUS_ID_USAGE_STATS, callingPid, callingUid)
+                == PackageManager.PERMISSION_GRANTED);
+    }
+
     private static void deleteRecursively(File f) {
         File[] files = f.listFiles();
         if (files != null) {
@@ -1030,7 +1039,8 @@
      * Called by the Binder stub.
      */
     UsageEvents queryEvents(int userId, long beginTime, long endTime,
-            boolean shouldObfuscateInstantApps, boolean shouldHideShortcutInvocationEvents) {
+            boolean shouldObfuscateInstantApps, boolean shouldHideShortcutInvocationEvents,
+            boolean shouldHideLocusIdEvents) {
         synchronized (mLock) {
             if (!mUserUnlockedStates.get(userId)) {
                 Slog.w(TAG, "Failed to query events for locked user " + userId);
@@ -1042,7 +1052,7 @@
                 return null; // user was stopped or removed
             }
             return service.queryEvents(beginTime, endTime, shouldObfuscateInstantApps,
-                    shouldHideShortcutInvocationEvents);
+                    shouldHideShortcutInvocationEvents, shouldHideLocusIdEvents);
         }
     }
 
@@ -1465,8 +1475,10 @@
             try {
                 final boolean hideShortcutInvocationEvents = shouldHideShortcutInvocationEvents(
                         userId, callingPackage, callingPid, callingUid);
+                boolean shouldHideLocusIdEvents = shouldHideLocusIdEvents(callingPid, callingUid);
                 return UsageStatsService.this.queryEvents(userId, beginTime, endTime,
-                        obfuscateInstantApps, hideShortcutInvocationEvents);
+                        obfuscateInstantApps, hideShortcutInvocationEvents,
+                        shouldHideLocusIdEvents);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -1513,8 +1525,10 @@
             try {
                 final boolean hideShortcutInvocationEvents = shouldHideShortcutInvocationEvents(
                         userId, callingPackage, callingPid, callingUid);
+                boolean shouldHideLocusIdEvents = shouldHideLocusIdEvents(callingPid, callingUid);
                 return UsageStatsService.this.queryEvents(userId, beginTime, endTime,
-                        obfuscateInstantApps, hideShortcutInvocationEvents);
+                        obfuscateInstantApps, hideShortcutInvocationEvents,
+                        shouldHideLocusIdEvents);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -2131,9 +2145,11 @@
 
         @Override
         public UsageEvents queryEventsForUser(int userId, long beginTime, long endTime,
-                boolean obfuscateInstantApps, boolean hideShortcutInvocationEvents) {
+                boolean obfuscateInstantApps, boolean shouldHideShortcutInvocationEvents,
+                boolean shouldHideLocusIdEvents) {
             return UsageStatsService.this.queryEvents(
-                    userId, beginTime, endTime, obfuscateInstantApps, hideShortcutInvocationEvents);
+                    userId, beginTime, endTime, obfuscateInstantApps,
+                    shouldHideShortcutInvocationEvents, shouldHideLocusIdEvents);
         }
 
         @Override
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 4d71112..d9317ac 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -481,8 +481,8 @@
         return queryStats(bucketType, beginTime, endTime, sEventStatsCombiner);
     }
 
-    UsageEvents queryEvents(final long beginTime, final long endTime,
-                            boolean obfuscateInstantApps, boolean hideShortcutInvocationEvents) {
+    UsageEvents queryEvents(final long beginTime, final long endTime, boolean obfuscateInstantApps,
+            boolean hideShortcutInvocationEvents, boolean hideLocusIdEvents) {
         if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) {
             return null;
         }
@@ -504,6 +504,10 @@
                                     && event.mEventType == Event.SHORTCUT_INVOCATION) {
                                 continue;
                             }
+                            if (hideLocusIdEvents
+                                    && event.mEventType == Event.LOCUS_ID_SET) {
+                                continue;
+                            }
                             if (obfuscateInstantApps) {
                                 event = event.getObfuscatedIfInstantApp();
                             }
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index ec99f36..52213d8 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -17,6 +17,7 @@
 package android.telecom;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
@@ -458,8 +459,14 @@
         /** Call supports the deflect feature. */
         public static final int CAPABILITY_SUPPORT_DEFLECT = 0x01000000;
 
+        /**
+         * Call supports adding participants to the call via
+         * {@link #addConferenceParticipants(List)}.
+         * @hide
+         */
+        public static final int CAPABILITY_ADD_PARTICIPANT = 0x02000000;
         //******************************************************************************************
-        // Next CAPABILITY value: 0x02000000
+        // Next CAPABILITY value: 0x04000000
         //******************************************************************************************
 
         /**
@@ -539,7 +546,7 @@
          *
          * @see TelecomManager#EXTRA_USE_ASSISTED_DIALING
          */
-        public static final int PROPERTY_ASSISTED_DIALING_USED = 0x00000200;
+        public static final int PROPERTY_ASSISTED_DIALING = 0x00000200;
 
         /**
          * Indicates that the call is an RTT call. Use {@link #getRttCall()} to get the
@@ -689,6 +696,9 @@
             if (can(capabilities, CAPABILITY_SUPPORT_DEFLECT)) {
                 builder.append(" CAPABILITY_SUPPORT_DEFLECT");
             }
+            if (can(capabilities, CAPABILITY_ADD_PARTICIPANT)) {
+                builder.append(" CAPABILITY_ADD_PARTICIPANT");
+            }
             builder.append("]");
             return builder.toString();
         }
@@ -744,7 +754,7 @@
             if (hasProperty(properties, PROPERTY_HAS_CDMA_VOICE_PRIVACY)) {
                 builder.append(" PROPERTY_HAS_CDMA_VOICE_PRIVACY");
             }
-            if (hasProperty(properties, PROPERTY_ASSISTED_DIALING_USED)) {
+            if (hasProperty(properties, PROPERTY_ASSISTED_DIALING)) {
                 builder.append(" PROPERTY_ASSISTED_DIALING_USED");
             }
             if (hasProperty(properties, PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL)) {
@@ -1703,6 +1713,17 @@
     }
 
     /**
+     * Pulls participants to existing call by forming a conference call.
+     * See {@link Details#CAPABILITY_ADD_PARTICIPANT}.
+     *
+     * @param participants participants to be pulled to existing call.
+     * @hide
+     */
+    public void addConferenceParticipants(@NonNull List<Uri> participants) {
+        mInCallAdapter.addConferenceParticipants(mTelecomCallId, participants);
+    }
+
+    /**
      * Initiates a request to the {@link ConnectionService} to pull an external call to the local
      * device.
      * <p>
diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java
index 56acdff..f019a9d 100644
--- a/telecomm/java/android/telecom/Conference.java
+++ b/telecomm/java/android/telecom/Conference.java
@@ -16,8 +16,13 @@
 
 package android.telecom;
 
+import static android.Manifest.permission.MODIFY_PHONE_STATE;
+
+import android.annotation.ElapsedRealtimeLong;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.net.Uri;
@@ -319,6 +324,13 @@
     public void onConnectionAdded(Connection connection) {}
 
     /**
+     * Notifies the {@link Conference} of a request to add a new participants to the conference call
+     * @param participants that will be added to this conference call
+     * @hide
+     */
+    public void onAddConferenceParticipants(@NonNull List<Uri> participants) {}
+
+    /**
      * Notifies this Conference, which is in {@code STATE_RINGING}, of
      * a request to accept.
      * For managed {@link ConnectionService}s, this will be called when the user answers a call via
@@ -625,12 +637,12 @@
      * Should be specified in wall-clock time returned by {@link System#currentTimeMillis()}.
      * <p>
      * When setting the connection time, you should always set the connection elapsed time via
-     * {@link #setConnectionStartElapsedRealTime(long)} to ensure the duration is reflected.
+     * {@link #setConnectionStartElapsedRealtimeMillis(long)} to ensure the duration is reflected.
      *
      * @param connectionTimeMillis The connection time, in milliseconds, as returned by
      *                             {@link System#currentTimeMillis()}.
      */
-    public final void setConnectionTime(long connectionTimeMillis) {
+    public final void setConnectionTime(@IntRange(from = 0) long connectionTimeMillis) {
         mConnectTimeMillis = connectionTimeMillis;
     }
 
@@ -646,8 +658,28 @@
      *
      * @param connectionStartElapsedRealTime The connection time, as measured by
      * {@link SystemClock#elapsedRealtime()}.
+     * @deprecated use {@link #setConnectionStartElapsedRealtimeMillis(long)} instead.
      */
+    @Deprecated
     public final void setConnectionStartElapsedRealTime(long connectionStartElapsedRealTime) {
+        setConnectionStartElapsedRealtimeMillis(connectionStartElapsedRealTime);
+    }
+
+    /**
+     * Sets the start time of the {@link Conference} which is the basis for the determining the
+     * duration of the {@link Conference}.
+     * <p>
+     * You should use a value returned by {@link SystemClock#elapsedRealtime()} to ensure that time
+     * zone changes do not impact the conference duration.
+     * <p>
+     * When setting this, you should also set the connection time via
+     * {@link #setConnectionTime(long)}.
+     *
+     * @param connectionStartElapsedRealTime The connection time, as measured by
+     * {@link SystemClock#elapsedRealtime()}.
+     */
+    public final void setConnectionStartElapsedRealtimeMillis(
+            @ElapsedRealtimeLong long connectionStartElapsedRealTime) {
         mConnectionStartElapsedRealTime = connectionStartElapsedRealTime;
     }
 
@@ -668,7 +700,7 @@
      *
      * @return The time at which the {@code Conference} was connected.
      */
-    public final long getConnectionTime() {
+    public final @IntRange(from = 0) long getConnectionTime() {
         return mConnectTimeMillis;
     }
 
@@ -685,11 +717,8 @@
      * has no general use other than to the Telephony framework.
      *
      * @return The elapsed time at which the {@link Conference} was connected.
-     * @hide
      */
-    @SystemApi
-    @TestApi
-    public final long getConnectionStartElapsedRealTime() {
+    public final @ElapsedRealtimeLong long getConnectionStartElapsedRealtimeMillis() {
         return mConnectionStartElapsedRealTime;
     }
 
@@ -987,6 +1016,7 @@
      */
     @SystemApi
     @TestApi
+    @RequiresPermission(MODIFY_PHONE_STATE)
     public void setConferenceState(boolean isConference) {
         for (Listener l : mListeners) {
             l.onConferenceStateChanged(this, isConference);
@@ -1007,6 +1037,7 @@
      */
     @SystemApi
     @TestApi
+    @RequiresPermission(MODIFY_PHONE_STATE)
     public final void setAddress(@NonNull Uri address,
             @TelecomManager.Presentation int presentation) {
         Log.d(this, "setAddress %s", address);
@@ -1113,12 +1144,52 @@
     }
 
     /**
-     * Sends an event associated with this {@code Conference} with associated event extras to the
-     * {@link InCallService} (note: this is identical in concept to
-     * {@link Connection#sendConnectionEvent(String, Bundle)}).
-     * @see Connection#sendConnectionEvent(String, Bundle)
+     * Sends an event associated with this {@link Conference} with associated event extras to the
+     * {@link InCallService}.
+     * <p>
+     * Connection events are used to communicate point in time information from a
+     * {@link ConnectionService} to an {@link InCallService} implementation.  An example of a
+     * custom connection event includes notifying the UI when a WIFI call has been handed over to
+     * LTE, which the InCall UI might use to inform the user that billing charges may apply.  The
+     * Android Telephony framework will send the {@link Connection#EVENT_MERGE_COMPLETE}
+     * connection event when a call to {@link Call#mergeConference()} has completed successfully.
+     * <p>
+     * Events are exposed to {@link InCallService} implementations via
+     * {@link Call.Callback#onConnectionEvent(Call, String, Bundle)}.
+     * <p>
+     * No assumptions should be made as to how an In-Call UI or service will handle these events.
+     * The {@link ConnectionService} must assume that the In-Call UI could even chose to ignore
+     * some events altogether.
+     * <p>
+     * Events should be fully qualified (e.g. {@code com.example.event.MY_EVENT}) to avoid
+     * conflicts between {@link ConnectionService} implementations.  Further, custom
+     * {@link ConnectionService} implementations shall not re-purpose events in the
+     * {@code android.*} namespace, nor shall they define new event types in this namespace.  When
+     * defining a custom event type, ensure the contents of the extras {@link Bundle} is clearly
+     * defined.  Extra keys for this bundle should be named similar to the event type (e.g.
+     * {@code com.example.extra.MY_EXTRA}).
+     * <p>
+     * When defining events and the associated extras, it is important to keep their behavior
+     * consistent when the associated {@link ConnectionService} is updated.  Support for deprecated
+     * events/extras should me maintained to ensure backwards compatibility with older
+     * {@link InCallService} implementations which were built to support the older behavior.
+     * <p>
+     * Expected connection events from the Telephony stack are:
+     * <p>
+     * <ul>
+     *      <li>{@link Connection#EVENT_CALL_HOLD_FAILED} with {@code null} {@code extras} when the
+     *      {@link Conference} could not be held.</li>
+     *      <li>{@link Connection#EVENT_MERGE_START} with {@code null} {@code extras} when a new
+     *      call is being merged into the conference.</li>
+     *      <li>{@link Connection#EVENT_MERGE_COMPLETE} with {@code null} {@code extras} a new call
+     *      has completed being merged into the conference.</li>
+     *      <li>{@link Connection#EVENT_CALL_MERGE_FAILED} with {@code null} {@code extras} a new
+     *      call has failed to merge into the conference (the dialer app can determine which call
+     *      failed to merge based on the fact that the call still exists outside of the conference
+     *      at the end of the merge process).</li>
+     * </ul>
      *
-     * @param event The connection event.
+     * @param event The conference event.
      * @param extras Optional bundle containing extra information associated with the event.
      */
     public void sendConferenceEvent(@NonNull String event, @Nullable Bundle extras) {
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 8049459..3b0ba25 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -16,9 +16,14 @@
 
 package android.telecom;
 
+import static android.Manifest.permission.MODIFY_PHONE_STATE;
+
+import android.annotation.ElapsedRealtimeLong;
 import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.app.Notification;
@@ -376,8 +381,14 @@
     /** Call supports the deflect feature. */
     public static final int CAPABILITY_SUPPORT_DEFLECT = 0x02000000;
 
+    /**
+     * When set, indicates that this {@link Connection} supports initiation of a conference call
+     * by directly adding participants using {@link #onAddConferenceParticipants(List)}.
+     * @hide
+     */
+    public static final int CAPABILITY_ADD_PARTICIPANT = 0x04000000;
     //**********************************************************************************************
-    // Next CAPABILITY value: 0x04000000
+    // Next CAPABILITY value: 0x08000000
     //**********************************************************************************************
 
     /**
@@ -474,7 +485,7 @@
      *
      * @see TelecomManager#EXTRA_USE_ASSISTED_DIALING
      */
-    public static final int PROPERTY_ASSISTED_DIALING_USED = 1 << 9;
+    public static final int PROPERTY_ASSISTED_DIALING = 1 << 9;
 
     /**
      * Set by the framework to indicate that the network has identified a Connection as an emergency
@@ -953,7 +964,9 @@
         if ((capabilities & CAPABILITY_SUPPORT_DEFLECT) == CAPABILITY_SUPPORT_DEFLECT) {
             builder.append(isLong ? " CAPABILITY_SUPPORT_DEFLECT" : " sup_def");
         }
-
+        if ((capabilities & CAPABILITY_ADD_PARTICIPANT) == CAPABILITY_ADD_PARTICIPANT) {
+            builder.append(isLong ? " CAPABILITY_ADD_PARTICIPANT" : " add_participant");
+        }
         builder.append("]");
         return builder.toString();
     }
@@ -2109,19 +2122,24 @@
      */
     @SystemApi
     @TestApi
-    public final long getConnectTimeMillis() {
+    public final @IntRange(from = 0) long getConnectTimeMillis() {
         return mConnectTimeMillis;
     }
 
     /**
      * Retrieves the connection start time of the {@link Connection}, if specified.  A value of
      * {@link Conference#CONNECT_TIME_NOT_SPECIFIED} indicates that Telecom should determine the
-     * start time of the conference.
+     * start time of the connection.
      * <p>
      * Based on the value of {@link SystemClock#elapsedRealtime()}, which ensures that wall-clock
      * changes do not impact the call duration.
      * <p>
      * Used internally in Telephony when migrating conference participant data for IMS conferences.
+     * <p>
+     * The value returned is the same one set using
+     * {@link #setConnectionStartElapsedRealtimeMillis(long)}.  This value is never updated from
+     * the Telecom framework, so no permission enforcement occurs when retrieving the value with
+     * this method.
      *
      * @return The time at which the {@link Connection} was connected.
      *
@@ -2129,7 +2147,7 @@
      */
     @SystemApi
     @TestApi
-    public final long getConnectElapsedTimeMillis() {
+    public final @ElapsedRealtimeLong long getConnectionStartElapsedRealtimeMillis() {
         return mConnectElapsedTimeMillis;
     }
 
@@ -2550,6 +2568,9 @@
      * Sets the time at which a call became active on this Connection. This is set only
      * when a conference call becomes active on this connection.
      * <p>
+     * This time corresponds to the date/time of connection and is stored in the call log in
+     * {@link android.provider.CallLog.Calls#DATE}.
+     * <p>
      * Used by telephony to maintain calls associated with an IMS Conference.
      *
      * @param connectTimeMillis The connection time, in milliseconds.  Should be set using a value
@@ -2559,7 +2580,8 @@
      */
     @SystemApi
     @TestApi
-    public final void setConnectTimeMillis(long connectTimeMillis) {
+    @RequiresPermission(MODIFY_PHONE_STATE)
+    public final void setConnectTimeMillis(@IntRange(from = 0) long connectTimeMillis) {
         mConnectTimeMillis = connectTimeMillis;
     }
 
@@ -2567,15 +2589,23 @@
      * Sets the time at which a call became active on this Connection. This is set only
      * when a conference call becomes active on this connection.
      * <p>
+     * This time is used to establish the duration of a call.  It uses
+     * {@link SystemClock#elapsedRealtime()} to ensure that the call duration is not impacted by
+     * time zone changes during a call.  The difference between the current
+     * {@link SystemClock#elapsedRealtime()} and the value set at the connection start time is used
+     * to populate {@link android.provider.CallLog.Calls#DURATION} in the call log.
+     * <p>
      * Used by telephony to maintain calls associated with an IMS Conference.
+     *
      * @param connectElapsedTimeMillis The connection time, in milliseconds.  Stored in the format
      *                              {@link SystemClock#elapsedRealtime()}.
-     *
      * @hide
      */
     @SystemApi
     @TestApi
-    public final void setConnectionStartElapsedRealTime(long connectElapsedTimeMillis) {
+    @RequiresPermission(MODIFY_PHONE_STATE)
+    public final void setConnectionStartElapsedRealtimeMillis(
+            @ElapsedRealtimeLong long connectElapsedTimeMillis) {
         mConnectElapsedTimeMillis = connectElapsedTimeMillis;
     }
 
@@ -2953,6 +2983,14 @@
     public void onSeparate() {}
 
     /**
+     * Supports initiation of a conference call by directly adding participants to an ongoing call.
+     *
+     * @param participants with which conference call will be formed.
+     * @hide
+     */
+    public void onAddConferenceParticipants(@NonNull List<Uri> participants) {}
+
+    /**
      * Notifies this Connection of a request to abort.
      */
     public void onAbort() {}
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 00c2918..2aea723 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SystemApi;
 import android.app.Service;
@@ -142,6 +141,7 @@
     private static final String SESSION_SPLIT_CONFERENCE = "CS.sFC";
     private static final String SESSION_MERGE_CONFERENCE = "CS.mC";
     private static final String SESSION_SWAP_CONFERENCE = "CS.sC";
+    private static final String SESSION_ADD_PARTICIPANT = "CS.aP";
     private static final String SESSION_POST_DIAL_CONT = "CS.oPDC";
     private static final String SESSION_PULL_EXTERNAL_CALL = "CS.pEC";
     private static final String SESSION_SEND_CALL_EVENT = "CS.sCE";
@@ -195,6 +195,7 @@
     private static final int MSG_CREATE_CONFERENCE_COMPLETE = 36;
     private static final int MSG_CREATE_CONFERENCE_FAILED = 37;
     private static final int MSG_REJECT_WITH_REASON = 38;
+    private static final int MSG_ADD_PARTICIPANT = 39;
 
     private static Connection sNullConnection;
 
@@ -627,6 +628,21 @@
         }
 
         @Override
+        public void addConferenceParticipants(String callId, List<Uri> participants,
+                Session.Info sessionInfo) {
+            Log.startSession(sessionInfo, SESSION_ADD_PARTICIPANT);
+            try {
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = callId;
+                args.arg2 = participants;
+                args.arg3 = Log.createSubsession();
+                mHandler.obtainMessage(MSG_ADD_PARTICIPANT, args).sendToTarget();
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        @Override
         public void onPostDialContinue(String callId, boolean proceed, Session.Info sessionInfo) {
             Log.startSession(sessionInfo, SESSION_POST_DIAL_CONT);
             try {
@@ -1224,6 +1240,19 @@
                     }
                     break;
                 }
+                case MSG_ADD_PARTICIPANT: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        Log.continueSession((Session) args.arg3,
+                                SESSION_HANDLER + SESSION_ADD_PARTICIPANT);
+                        addConferenceParticipants((String) args.arg1, (List<Uri>)args.arg2);
+                    } finally {
+                        args.recycle();
+                        Log.endSession();
+                    }
+                    break;
+                }
+
                 case MSG_ON_POST_DIAL_CONTINUE: {
                     SomeArgs args = (SomeArgs) msg.obj;
                     try {
@@ -1778,7 +1807,7 @@
                         null : conference.getVideoProvider().getInterface(),
                 conference.getVideoState(),
                 conference.getConnectTimeMillis(),
-                conference.getConnectionStartElapsedRealTime(),
+                conference.getConnectionStartElapsedRealtimeMillis(),
                 conference.getStatusHints(),
                 conference.getExtras(),
                 conference.getAddress(),
@@ -1884,7 +1913,7 @@
                         connection.isRingbackRequested(),
                         connection.getAudioModeIsVoip(),
                         connection.getConnectTimeMillis(),
-                        connection.getConnectElapsedTimeMillis(),
+                        connection.getConnectionStartElapsedRealtimeMillis(),
                         connection.getStatusHints(),
                         connection.getDisconnectCause(),
                         createIdList(connection.getConferenceables()),
@@ -2152,6 +2181,17 @@
         }
     }
 
+    private void addConferenceParticipants(String callId, List<Uri> participants) {
+        Log.d(this, "addConferenceParticipants(%s)", callId);
+        if (mConnectionById.containsKey(callId)) {
+            findConnectionForAction(callId, "addConferenceParticipants")
+                    .onAddConferenceParticipants(participants);
+        } else {
+            findConferenceForAction(callId, "addConferenceParticipants")
+                    .onAddConferenceParticipants(participants);
+        }
+    }
+
     /**
      * Notifies a {@link Connection} of a request to pull an external call.
      *
@@ -2374,7 +2414,7 @@
                             null : conference.getVideoProvider().getInterface(),
                     conference.getVideoState(),
                     conference.getConnectTimeMillis(),
-                    conference.getConnectionStartElapsedRealTime(),
+                    conference.getConnectionStartElapsedRealtimeMillis(),
                     conference.getStatusHints(),
                     conference.getExtras(),
                     conference.getAddress(),
@@ -2465,7 +2505,7 @@
                     connection.isRingbackRequested(),
                     connection.getAudioModeIsVoip(),
                     connection.getConnectTimeMillis(),
-                    connection.getConnectElapsedTimeMillis(),
+                    connection.getConnectionStartElapsedRealtimeMillis(),
                     connection.getStatusHints(),
                     connection.getDisconnectCause(),
                     emptyList,
diff --git a/telecomm/java/android/telecom/InCallAdapter.java b/telecomm/java/android/telecom/InCallAdapter.java
index 594c1eb..9d291740 100644
--- a/telecomm/java/android/telecom/InCallAdapter.java
+++ b/telecomm/java/android/telecom/InCallAdapter.java
@@ -283,6 +283,20 @@
     }
 
     /**
+     * Instructs Telecom to pull participants to existing call
+     *
+     * @param callId The unique ID of the call.
+     * @param participants participants to be pulled to existing call.
+     */
+    public void addConferenceParticipants(String callId, List<Uri> participants) {
+        try {
+            mAdapter.addConferenceParticipants(callId, participants);
+        } catch (RemoteException ignored) {
+        }
+    }
+
+
+    /**
      * Instructs Telecom to split the specified call from any conference call with which it may be
      * connected.
      *
diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java
index ebfa3a1..982e5f3 100644
--- a/telecomm/java/android/telecom/InCallService.java
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -20,6 +20,7 @@
 import android.annotation.SdkConstant;
 import android.annotation.SystemApi;
 import android.app.Service;
+import android.app.UiModeManager;
 import android.bluetooth.BluetoothDevice;
 import android.content.Intent;
 import android.hardware.camera2.CameraManager;
@@ -43,12 +44,32 @@
  * phone calls.
  * <h2>Becoming the Default Phone App</h2>
  * The default dialer/phone app is one which provides the in-call user interface while the device is
- * in a call.  A device is bundled with a system provided default dialer/phone app.  The user may
- * choose a single app to take over this role from the system app.  An app which wishes to fulfill
- * one this role uses the {@code android.app.role.RoleManager} to request that they fill the role.
+ * in a call.  It also provides the user with a means to initiate calls and see a history of calls
+ * on their device.  A device is bundled with a system provided default dialer/phone app.  The user
+ * may choose a single app to take over this role from the system app.  An app which wishes to
+ * fulfill one this role uses the {@link android.app.role.RoleManager} to request that they fill the
+ * {@link android.app.role.RoleManager#ROLE_DIALER} role.
  * <p>
- * An app filling the role of the default phone app provides a user interface while the device is in
- * a call, and the device is not in car mode.
+ * The default phone app provides a user interface while the device is in a call, and the device is
+ * not in car mode (i.e. {@link UiModeManager#getCurrentModeType()} is not
+ * {@link android.content.res.Configuration#UI_MODE_TYPE_CAR}).
+ * <p>
+ * In order to fill the {@link android.app.role.RoleManager#ROLE_DIALER} role, an app must meet a
+ * number of requirements:
+ * <ul>
+ *     <li>It must handle the {@link Intent#ACTION_DIAL} intent.  This means the app must provide
+ *     a dial pad UI for the user to initiate outgoing calls.</li>
+ *     <li>It must fully implement the {@link InCallService} API and provide both an incoming call
+ *     UI, as well as an ongoing call UI.</li>
+ * </ul>
+ * <p>
+ * Note: If the app filling the {@link android.app.role.RoleManager#ROLE_DIALER} crashes during
+ * {@link InCallService} binding, the Telecom framework will automatically fall back to using the
+ * dialer app pre-loaded on the device.  The system will display a notification to the user to let
+ * them know that the app has crashed and that their call was continued using the pre-loaded dialer
+ * app.
+ * <p>
+ * Further, the pre-loaded dialer will ALWAYS be used when the user places an emergency call.
  * <p>
  * Below is an example manifest registration for an {@code InCallService}. The meta-data
  * {@link TelecomManager#METADATA_IN_CALL_SERVICE_UI} indicates that this particular
@@ -82,6 +103,11 @@
  *           <action android:name="android.intent.action.DIAL" />
  *           <category android:name="android.intent.category.DEFAULT" />
  *      </intent-filter>
+ *      <intent-filter>
+ *           <action android:name="android.intent.action.DIAL" />
+ *           <category android:name="android.intent.category.DEFAULT" />
+ *           <data android:scheme="tel" />
+ *      </intent-filter>
  * </activity>
  * }
  * </pre>
@@ -111,6 +137,7 @@
  *         }
  *     }
  * }
+ * }
  * </pre>
  * <p id="incomingCallNotification">
  * <h3>Showing the Incoming Call Notification</h3>
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index f00432b..4e6e1a5 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -16,7 +16,10 @@
 
 package android.telecom;
 
+import static android.Manifest.permission.MODIFY_PHONE_STATE;
+
 import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.content.Intent;
@@ -614,7 +617,8 @@
          * time. By default, there is no group Id for a {@link PhoneAccount} (an empty String). Only
          * grouped {@link PhoneAccount}s with the same {@link ConnectionService} can be replaced.
          * <p>
-         * Note: This is an API specific to the Telephony stack.
+         * Note: This is an API specific to the Telephony stack; the group Id will be ignored for
+         * callers not holding the correct permission.
          *
          * @param groupId The group Id of the {@link PhoneAccount} that will replace any other
          * registered {@link PhoneAccount} in Telecom with the same Group Id.
@@ -623,6 +627,7 @@
          */
         @SystemApi
         @TestApi
+        @RequiresPermission(MODIFY_PHONE_STATE)
         public @NonNull Builder setGroupId(@NonNull String groupId) {
             if (groupId != null) {
                 mGroupId = groupId;
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index a28cc4f..5d7d649 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -819,8 +819,8 @@
      * automatically add dialing prefixes when placing international calls.
      * <p>
      * Setting this extra on the outgoing call extras will cause the
-     * {@link Connection#PROPERTY_ASSISTED_DIALING_USED} property and
-     * {@link Call.Details#PROPERTY_ASSISTED_DIALING_USED} property to be set on the
+     * {@link Connection#PROPERTY_ASSISTED_DIALING} property and
+     * {@link Call.Details#PROPERTY_ASSISTED_DIALING} property to be set on the
      * {@link Connection}/{@link Call} in question.  When the call is logged to the call log, the
      * {@link android.provider.CallLog.Calls#FEATURES_ASSISTED_DIALING_USED} call feature is set to
      * indicate that assisted dialing was used for the call.
@@ -1412,7 +1412,7 @@
     /**
      * Used to determine the currently selected default dialer package for a specific user.
      *
-     * @param userId the user id to query the default dialer package for.
+     * @param userHandle the user id to query the default dialer package for.
      * @return package name for the default dialer package or null if no package has been
      *         selected as the default dialer.
      * @hide
@@ -1420,10 +1420,11 @@
     @SystemApi
     @TestApi
     @RequiresPermission(READ_PRIVILEGED_PHONE_STATE)
-    public @Nullable String getDefaultDialerPackage(int userId) {
+    public @Nullable String getDefaultDialerPackage(@NonNull UserHandle userHandle) {
         try {
             if (isServiceConnected()) {
-                return getTelecomService().getDefaultDialerPackageForUser(userId);
+                return getTelecomService().getDefaultDialerPackageForUser(
+                        userHandle.getIdentifier());
             }
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException attempting to get the default dialer package name.", e);
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
index 4249dff..a397d77 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
@@ -104,6 +104,9 @@
 
     void swapConference(String conferenceCallId, in Session.Info sessionInfo);
 
+    void addConferenceParticipants(String CallId, in List<Uri> participants,
+    in Session.Info sessionInfo);
+
     void onPostDialContinue(String callId, boolean proceed, in Session.Info sessionInfo);
 
     void pullExternalCall(String callId, in Session.Info sessionInfo);
diff --git a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
index eb2d714..9beff22 100644
--- a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
@@ -67,6 +67,8 @@
 
     void swapConference(String callId);
 
+    void addConferenceParticipants(String callId, in List<Uri> participants);
+
     void turnOnProximitySensor();
 
     void turnOffProximitySensor(boolean screenOnImmediately);
diff --git a/telephony/java/android/telephony/Annotation.java b/telephony/java/android/telephony/Annotation.java
index d2a5905..a27c480 100644
--- a/telephony/java/android/telephony/Annotation.java
+++ b/telephony/java/android/telephony/Annotation.java
@@ -661,4 +661,16 @@
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Skip464XlatStatus {}
+
+    /**
+     * Override network type
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "OVERRIDE_NETWORK_TYPE_", value = {
+            DisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
+            DisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA,
+            DisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO,
+            DisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+            DisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE})
+    public @interface OverrideNetworkType {}
 }
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index ebb53c5..51b4a31 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1090,6 +1090,14 @@
             "support_adhoc_conference_calls_bool";
 
     /**
+     * Determines whether conference participants can be added to existing call.  When {@code true},
+     * adding conference participants to existing call is supported, {@code false otherwise}.
+     * @hide
+     */
+    public static final String KEY_SUPPORT_ADD_CONFERENCE_PARTICIPANTS_BOOL =
+            "support_add_conference_participants_bool";
+
+    /**
      * Determines whether conference calls are supported by a carrier.  When {@code true},
      * conference calling is supported, {@code false otherwise}.
      */
@@ -4004,6 +4012,7 @@
         sDefaults.putBoolean(KEY_IGNORE_RTT_MODE_SETTING_BOOL, false);
         sDefaults.putInt(KEY_CDMA_3WAYCALL_FLASH_DELAY_INT , 0);
         sDefaults.putBoolean(KEY_SUPPORT_ADHOC_CONFERENCE_CALLS_BOOL, false);
+        sDefaults.putBoolean(KEY_SUPPORT_ADD_CONFERENCE_PARTICIPANTS_BOOL, false);
         sDefaults.putBoolean(KEY_SUPPORT_CONFERENCE_CALL_BOOL, true);
         sDefaults.putBoolean(KEY_SUPPORT_IMS_CONFERENCE_CALL_BOOL, true);
         sDefaults.putBoolean(KEY_SUPPORT_MANAGE_IMS_CONFERENCE_CALL_BOOL, true);
diff --git a/telephony/java/android/telephony/CellInfo.java b/telephony/java/android/telephony/CellInfo.java
index ec86c14..bfa209b 100644
--- a/telephony/java/android/telephony/CellInfo.java
+++ b/telephony/java/android/telephony/CellInfo.java
@@ -16,9 +16,9 @@
 
 package android.telephony;
 
+import android.annotation.ElapsedRealtimeLong;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.SuppressLint;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.hardware.radio.V1_4.CellInfo.Info;
 import android.os.Parcel;
@@ -178,18 +178,18 @@
     /**
      * Approximate time this cell information was received from the modem.
      *
-     * @return a time stamp in nanos since boot.
+     * @return a time stamp in millis since boot.
      */
-    @SuppressLint("MethodNameUnits")
-    public long getTimestampNanos() {
-        return mTimeStamp;
+    @ElapsedRealtimeLong
+    public long getTimestampMillis() {
+        return mTimeStamp / 1000000;
     }
 
     /**
      * Approximate time this cell information was received from the modem.
      *
      * @return a time stamp in nanos since boot.
-     * @deprecated Use {@link #getTimestampNanos} instead.
+     * @deprecated Use {@link #getTimestampMillis} instead.
      */
     @Deprecated
     public long getTimeStamp() {
diff --git a/telephony/java/android/telephony/DisplayInfo.aidl b/telephony/java/android/telephony/DisplayInfo.aidl
new file mode 100644
index 0000000..861b0fe
--- /dev/null
+++ b/telephony/java/android/telephony/DisplayInfo.aidl
@@ -0,0 +1,18 @@
+/*
+ * 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.telephony;
+
+parcelable DisplayInfo;
diff --git a/telephony/java/android/telephony/DisplayInfo.java b/telephony/java/android/telephony/DisplayInfo.java
new file mode 100644
index 0000000..d54bcf9
--- /dev/null
+++ b/telephony/java/android/telephony/DisplayInfo.java
@@ -0,0 +1,172 @@
+/*
+ * 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.
+ */
+
+package android.telephony;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.telephony.Annotation.NetworkType;
+import android.telephony.Annotation.OverrideNetworkType;
+
+import java.util.Objects;
+
+/**
+ * DisplayInfo contains telephony-related information used for display purposes only. This
+ * information is provided in accordance with carrier policy and branding preferences; it is not
+ * necessarily a precise or accurate representation of the current state and should be treated
+ * accordingly.
+ */
+public final class DisplayInfo implements Parcelable {
+    /**
+     * No override. {@link #getNetworkType()} should be used for display network
+     * type.
+     */
+    public static final int OVERRIDE_NETWORK_TYPE_NONE = 0;
+
+    /**
+     * Override network type when the device is connected to
+     * {@link TelephonyManager#NETWORK_TYPE_LTE} cellular network and is using carrier aggregation.
+     */
+    public static final int OVERRIDE_NETWORK_TYPE_LTE_CA = 1;
+
+    /**
+     * Override network type when the device is connected to advanced pro
+     * {@link TelephonyManager#NETWORK_TYPE_LTE} cellular network.
+     */
+    public static final int OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO = 2;
+
+    /**
+     * Override network type when the device is connected to
+     * {@link TelephonyManager#NETWORK_TYPE_LTE} network and has E-UTRA-NR Dual Connectivity(EN-DC)
+     * capability or is currently connected to the secondary
+     * {@link TelephonyManager#NETWORK_TYPE_NR} cellular network.
+     */
+    public static final int OVERRIDE_NETWORK_TYPE_NR_NSA = 3;
+
+    /**
+     * Override network type when the device is connected to
+     * {@link TelephonyManager#NETWORK_TYPE_LTE} network and has E-UTRA-NR Dual Connectivity(EN-DC)
+     * capability or is currently connected to the secondary
+     * {@link TelephonyManager#NETWORK_TYPE_NR} cellular network on millimeter wave bands.
+     *
+     * @see AccessNetworkConstants.NgranBands#FREQUENCY_RANGE_GROUP_2
+     */
+    public static final int OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE = 4;
+
+    @NetworkType
+    private final  int mNetworkType;
+
+    @OverrideNetworkType
+    private final  int mOverrideNetworkType;
+
+    /**
+     * Constructor
+     *
+     * @param networkType Current packet-switching cellular network type
+     * @param overrideNetworkType The override network type
+     *
+     * @hide
+     */
+    public DisplayInfo(@NetworkType int networkType, @OverrideNetworkType int overrideNetworkType) {
+        mNetworkType = networkType;
+        mOverrideNetworkType = overrideNetworkType;
+    }
+
+    /** @hide */
+    public DisplayInfo(Parcel p) {
+        mNetworkType = p.readInt();
+        mOverrideNetworkType = p.readInt();
+    }
+
+    /**
+     * Get current packet-switching cellular network type. This is the actual network type the
+     * device is camped on.
+     *
+     * @return The network type.
+     */
+    @NetworkType
+    public int getNetworkType() {
+        return mNetworkType;
+    }
+
+    /**
+     * Get the override network type. Note the override network type is for market branding
+     * or visualization purposes only. It cannot be treated as the actual network type device is
+     * camped on.
+     *
+     * @return The override network type.
+     */
+    @OverrideNetworkType
+    public int getOverrideNetworkType() {
+        return mOverrideNetworkType;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mNetworkType);
+        dest.writeInt(mOverrideNetworkType);
+    }
+
+    public static final @NonNull Parcelable.Creator<DisplayInfo> CREATOR =
+            new Parcelable.Creator<DisplayInfo>() {
+                @Override
+                public DisplayInfo createFromParcel(Parcel source) {
+                    return new DisplayInfo(source);
+                }
+
+                @Override
+                public DisplayInfo[] newArray(int size) {
+                    return new DisplayInfo[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        DisplayInfo that = (DisplayInfo) o;
+        return mNetworkType == that.mNetworkType
+                && mOverrideNetworkType == that.mOverrideNetworkType;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mNetworkType, mOverrideNetworkType);
+    }
+
+    private static String overrideNetworkTypeToString(@OverrideNetworkType int type) {
+        switch (type) {
+            case OVERRIDE_NETWORK_TYPE_NONE: return "NONE";
+            case OVERRIDE_NETWORK_TYPE_LTE_CA: return "LTE_CA";
+            case OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO: return "LTE_ADV_PRO";
+            case OVERRIDE_NETWORK_TYPE_NR_NSA: return "NR_NSA";
+            case OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE: return "NR_NSA_MMWAVE";
+            default: return "UNKNOWN";
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "DisplayInfo {network=" + TelephonyManager.getNetworkTypeName(mNetworkType)
+                + ", override=" + overrideNetworkTypeToString(mOverrideNetworkType);
+    }
+}
diff --git a/telephony/java/android/telephony/PreciseDisconnectCause.java b/telephony/java/android/telephony/PreciseDisconnectCause.java
index 54980a2..250d9e8 100644
--- a/telephony/java/android/telephony/PreciseDisconnectCause.java
+++ b/telephony/java/android/telephony/PreciseDisconnectCause.java
@@ -256,337 +256,6 @@
     /** Access Blocked by CDMA network. */
     public static final int CDMA_ACCESS_BLOCKED                              = 1009;
 
-    /** Mapped from ImsReasonInfo */
-    // TODO: remove ImsReasonInfo from preciseDisconnectCause
-    /* The passed argument is an invalid */
-    /** @hide */
-    public static final int LOCAL_ILLEGAL_ARGUMENT                           = 1200;
-    // The operation is invoked in invalid call state
-    /** @hide */
-    public static final int LOCAL_ILLEGAL_STATE                              = 1201;
-    // IMS service internal error
-    /** @hide */
-    public static final int LOCAL_INTERNAL_ERROR                             = 1202;
-    // IMS service goes down (service connection is lost)
-    /** @hide */
-    public static final int LOCAL_IMS_SERVICE_DOWN                           = 1203;
-    // No pending incoming call exists
-    /** @hide */
-    public static final int LOCAL_NO_PENDING_CALL                            = 1204;
-    // Service unavailable; by power off
-    /** @hide */
-    public static final int LOCAL_POWER_OFF                                  = 1205;
-    // Service unavailable; by low battery
-    /** @hide */
-    public static final int LOCAL_LOW_BATTERY                                = 1206;
-    // Service unavailable; by out of service (data service state)
-    /** @hide */
-    public static final int LOCAL_NETWORK_NO_SERVICE                         = 1207;
-    /* Service unavailable; by no LTE coverage
-     * (VoLTE is not supported even though IMS is registered)
-     */
-    /** @hide */
-    public static final int LOCAL_NETWORK_NO_LTE_COVERAGE                    = 1208;
-    /** Service unavailable; by located in roaming area */
-    /** @hide */
-    public static final int LOCAL_NETWORK_ROAMING                            = 1209;
-    /** Service unavailable; by IP changed */
-    /** @hide */
-    public static final int LOCAL_NETWORK_IP_CHANGED                         = 1210;
-    /** Service unavailable; other */
-    /** @hide */
-    public static final int LOCAL_SERVICE_UNAVAILABLE                        = 1211;
-    /* Service unavailable; IMS connection is lost (IMS is not registered) */
-    /** @hide */
-    public static final int LOCAL_NOT_REGISTERED                             = 1212;
-    /** Max call exceeded */
-    /** @hide */
-    public static final int LOCAL_MAX_CALL_EXCEEDED                          = 1213;
-    /** Call decline */
-    /** @hide */
-    public static final int LOCAL_CALL_DECLINE                               = 1214;
-    /** SRVCC is in progress */
-    /** @hide */
-    public static final int LOCAL_CALL_VCC_ON_PROGRESSING                    = 1215;
-    /** Resource reservation is failed (QoS precondition) */
-    /** @hide */
-    public static final int LOCAL_CALL_RESOURCE_RESERVATION_FAILED           = 1216;
-    /** Retry CS call; VoLTE service can't be provided by the network or remote end
-     *  Resolve the extra code(EXTRA_CODE_CALL_RETRY_*) if the below code is set
-     *  @hide
-     */
-    public static final int LOCAL_CALL_CS_RETRY_REQUIRED                     = 1217;
-    /** Retry VoLTE call; VoLTE service can't be provided by the network temporarily */
-    /** @hide */
-    public static final int LOCAL_CALL_VOLTE_RETRY_REQUIRED                  = 1218;
-    /** IMS call is already terminated (in TERMINATED state) */
-    /** @hide */
-    public static final int LOCAL_CALL_TERMINATED                            = 1219;
-    /** Handover not feasible */
-    /** @hide */
-    public static final int LOCAL_HO_NOT_FEASIBLE                            = 1220;
-
-    /** 1xx waiting timer is expired after sending INVITE request (MO only) */
-    /** @hide */
-    public static final int TIMEOUT_1XX_WAITING                              = 1221;
-    /** User no answer during call setup operation (MO/MT)
-     *  MO : 200 OK to INVITE request is not received,
-     *  MT : No action from user after alerting the call
-     *  @hide
-     */
-    public static final int TIMEOUT_NO_ANSWER                                = 1222;
-    /** User no answer during call update operation (MO/MT)
-     *  MO : 200 OK to re-INVITE request is not received,
-     *  MT : No action from user after alerting the call
-     *  @hide
-     */
-    public static final int TIMEOUT_NO_ANSWER_CALL_UPDATE                    = 1223;
-
-    /**
-     * STATUSCODE (SIP response code) (IMS -> Telephony)
-     */
-    /** SIP request is redirected */
-    /** @hide */
-    public static final int SIP_REDIRECTED                                   = 1300;
-    /** 4xx responses */
-    /** 400 : Bad Request */
-    /** @hide */
-    public static final int SIP_BAD_REQUEST                                  = 1310;
-    /** 403 : Forbidden */
-    /** @hide */
-    public static final int SIP_FORBIDDEN                                    = 1311;
-    /** 404 : Not Found */
-    /** @hide */
-    public static final int SIP_NOT_FOUND                                    = 1312;
-    /** 415 : Unsupported Media Type
-     *  416 : Unsupported URI Scheme
-     *  420 : Bad Extension
-     */
-    /** @hide */
-    public static final int SIP_NOT_SUPPORTED                                = 1313;
-    /** 408 : Request Timeout */
-    /** @hide */
-    public static final int SIP_REQUEST_TIMEOUT                              = 1314;
-    /** 480 : Temporarily Unavailable */
-    /** @hide */
-    public static final int SIP_TEMPRARILY_UNAVAILABLE                       = 1315;
-    /** 484 : Address Incomplete */
-    /** @hide */
-    public static final int SIP_BAD_ADDRESS                                  = 1316;
-    /** 486 : Busy Here
-     *  600 : Busy Everywhere
-     */
-    /** @hide */
-    public static final int SIP_BUSY                                         = 1317;
-    /** 487 : Request Terminated */
-    /** @hide */
-    public static final int SIP_REQUEST_CANCELLED                            = 1318;
-    /** 406 : Not Acceptable
-     *  488 : Not Acceptable Here
-     *  606 : Not Acceptable
-     */
-    /** @hide */
-    public static final int SIP_NOT_ACCEPTABLE                               = 1319;
-    /** 410 : Gone
-     *  604 : Does Not Exist Anywhere
-     */
-    /** @hide */
-    public static final int SIP_NOT_REACHABLE                                = 1320;
-    /** Others */
-    /** @hide */
-    public static final int SIP_CLIENT_ERROR                                 = 1321;
-    /** 481 : Transaction Does Not Exist */
-    /** @hide */
-    public static final int SIP_TRANSACTION_DOES_NOT_EXIST                   = 1322;
-    /** 5xx responses
-     *  501 : Server Internal Error
-     */
-    /** @hide */
-    public static final int SIP_SERVER_INTERNAL_ERROR                        = 1330;
-    /** 503 : Service Unavailable */
-    /** @hide */
-    public static final int SIP_SERVICE_UNAVAILABLE                          = 1331;
-    /** 504 : Server Time-out */
-    /** @hide */
-    public static final int SIP_SERVER_TIMEOUT                               = 1332;
-    /** Others */
-    /** @hide */
-    public static final int SIP_SERVER_ERROR                                 = 1333;
-    /** 6xx responses
-     *  603 : Decline
-     */
-    /** @hide */
-    public static final int SIP_USER_REJECTED                                = 1340;
-    /** Others */
-    /** @hide */
-    public static final int SIP_GLOBAL_ERROR                                 = 1341;
-    /** Emergency failure */
-    /** @hide */
-    public static final int EMERGENCY_TEMP_FAILURE                           = 1342;
-    /** @hide */
-    public static final int EMERGENCY_PERM_FAILURE                           = 1343;
-    /** Media resource initialization failed */
-    /** @hide */
-    public static final int MEDIA_INIT_FAILED                                = 1400;
-    /** RTP timeout (no audio / video traffic in the session) */
-    /** @hide */
-    public static final int MEDIA_NO_DATA                                    = 1401;
-    /** Media is not supported; so dropped the call */
-    /** @hide */
-    public static final int MEDIA_NOT_ACCEPTABLE                             = 1402;
-    /** Unknown media related errors */
-    /** @hide */
-    public static final int MEDIA_UNSPECIFIED                                = 1403;
-    /** User triggers the call end */
-    /** @hide */
-    public static final int USER_TERMINATED                                  = 1500;
-    /** No action while an incoming call is ringing */
-    /** @hide */
-    public static final int USER_NOANSWER                                    = 1501;
-    /** User ignores an incoming call */
-    /** @hide */
-    public static final int USER_IGNORE                                      = 1502;
-    /** User declines an incoming call */
-    /** @hide */
-    public static final int USER_DECLINE                                     = 1503;
-    /** Device declines/ends a call due to low battery */
-    /** @hide */
-    public static final int LOW_BATTERY                                      = 1504;
-    /** Device declines call due to blacklisted call ID */
-    /** @hide */
-    public static final int BLACKLISTED_CALL_ID                              = 1505;
-    /** The call is terminated by the network or remote user */
-    /** @hide */
-    public static final int USER_TERMINATED_BY_REMOTE                        = 1510;
-
-    /**
-     * UT
-     */
-    /** @hide */
-    public static final int UT_NOT_SUPPORTED                                 = 1800;
-    /** @hide */
-    public static final int UT_SERVICE_UNAVAILABLE                           = 1801;
-    /** @hide */
-    public static final int UT_OPERATION_NOT_ALLOWED                         = 1802;
-    /** @hide */
-    public static final int UT_NETWORK_ERROR                                 = 1803;
-    /** @hide */
-    public static final int UT_CB_PASSWORD_MISMATCH                          = 1804;
-
-    /**
-     * ECBM
-     * @hide
-     */
-    public static final int ECBM_NOT_SUPPORTED                               = 1900;
-
-    /**
-     * Fail code used to indicate that Multi-endpoint is not supported by the Ims framework.
-     * @hide
-     */
-    public static final int MULTIENDPOINT_NOT_SUPPORTED                      = 1901;
-
-    /**
-     * CALL DROP error codes (Call could drop because of many reasons like Network not available,
-     *  handover, failed, etc)
-     */
-
-    /**
-     * CALL DROP error code for the case when a device is ePDG capable and when the user is on an
-     * active wifi call and at the edge of coverage and there is no qualified LTE network available
-     * to handover the call to. We get a handover NOT_TRIGERRED message from the modem. This error
-     * code is received as part of the handover message.
-     * @hide
-     */
-    public static final int CALL_DROP_IWLAN_TO_LTE_UNAVAILABLE               = 2000;
-
-    /**
-     * MT call has ended due to a release from the network
-     * because the call was answered elsewhere
-     * @hide
-     */
-    public static final int ANSWERED_ELSEWHERE                               = 2100;
-
-    /**
-     * For MultiEndpoint - Call Pull request has failed
-     * @hide
-     */
-    public static final int CALL_PULL_OUT_OF_SYNC                            = 2101;
-
-    /**
-     * For MultiEndpoint - Call has been pulled from primary to secondary
-     * @hide
-     */
-    public static final int CALL_PULLED                                      = 2102;
-
-    /**
-     * Supplementary services (HOLD/RESUME) failure error codes.
-     * Values for Supplemetary services failure - Failed, Cancelled and Re-Invite collision.
-     * @hide
-     */
-    public static final int SUPP_SVC_FAILED                                  = 2300;
-    /** @hide */
-    public static final int SUPP_SVC_CANCELLED                               = 2301;
-    /** @hide */
-    public static final int SUPP_SVC_REINVITE_COLLISION                      = 2302;
-
-    /**
-     * DPD Procedure received no response or send failed
-     * @hide
-     */
-    public static final int IWLAN_DPD_FAILURE                                = 2400;
-
-    /**
-     * Establishment of the ePDG Tunnel Failed
-     * @hide
-     */
-    public static final int EPDG_TUNNEL_ESTABLISH_FAILURE                    = 2500;
-
-    /**
-     * Re-keying of the ePDG Tunnel Failed; may not always result in teardown
-     * @hide
-     */
-    public static final int EPDG_TUNNEL_REKEY_FAILURE                        = 2501;
-
-    /**
-     * Connection to the packet gateway is lost
-     * @hide
-     */
-    public static final int EPDG_TUNNEL_LOST_CONNECTION                      = 2502;
-
-    /**
-     * The maximum number of calls allowed has been reached.  Used in a multi-endpoint scenario
-     * where the number of calls across all connected devices has reached the maximum.
-     * @hide
-     */
-    public static final int MAXIMUM_NUMBER_OF_CALLS_REACHED                  = 2503;
-
-    /**
-     * Similar to {@link #CODE_LOCAL_CALL_DECLINE}, except indicates that a remote device has
-     * declined the call.  Used in a multi-endpoint scenario where a remote device declined an
-     * incoming call.
-     * @hide
-     */
-    public static final int REMOTE_CALL_DECLINE                              = 2504;
-
-    /**
-     * Indicates the call was disconnected due to the user reaching their data limit.
-     * @hide
-     */
-    public static final int DATA_LIMIT_REACHED                               = 2505;
-
-    /**
-     * Indicates the call was disconnected due to the user disabling cellular data.
-     * @hide
-     */
-    public static final int DATA_DISABLED                                    = 2506;
-
-    /**
-     * Indicates a call was disconnected due to loss of wifi signal.
-     * @hide
-     */
-    public static final int WIFI_LOST                                        = 2507;
-
-
     /* OEM specific error codes. To be used by OEMs when they don't want to
        reveal error code which would be replaced by ERROR_UNSPECIFIED */
     public static final int OEM_CAUSE_1                                      = 0xf001;
diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java
index 5a4dac5..ad58c54 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -16,8 +16,8 @@
 
 package android.telephony;
 
+import android.annotation.ElapsedRealtimeLong;
 import android.annotation.NonNull;
-import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
@@ -79,8 +79,9 @@
     /* The type of signal measurement */
     private static final String MEASUREMENT_TYPE_RSCP = "rscp";
 
-    // timeStamp of signalStrength in nanoseconds since boot
-    private long mTimestamp = Long.MAX_VALUE;
+    // Timestamp of SignalStrength since boot
+    // Effectively final. Timestamp is set during construction of SignalStrength
+    private long mTimestampMillis;
 
     CellSignalStrengthCdma mCdma;
     CellSignalStrengthGsm mGsm;
@@ -139,7 +140,7 @@
         mTdscdma = tdscdma;
         mLte = lte;
         mNr = nr;
-        mTimestamp = SystemClock.elapsedRealtimeNanos();
+        mTimestampMillis = SystemClock.elapsedRealtime();
     }
 
     /**
@@ -274,7 +275,6 @@
         mTdscdma.updateLevel(cc, ss);
         mLte.updateLevel(cc, ss);
         mNr.updateLevel(cc, ss);
-        mTimestamp = SystemClock.elapsedRealtimeNanos();
     }
 
     /**
@@ -300,7 +300,7 @@
         mTdscdma = new CellSignalStrengthTdscdma(s.mTdscdma);
         mLte = new CellSignalStrengthLte(s.mLte);
         mNr = new CellSignalStrengthNr(s.mNr);
-        mTimestamp = s.getTimestampNanos();
+        mTimestampMillis = s.getTimestampMillis();
     }
 
     /**
@@ -318,7 +318,7 @@
         mTdscdma = in.readParcelable(CellSignalStrengthTdscdma.class.getClassLoader());
         mLte = in.readParcelable(CellSignalStrengthLte.class.getClassLoader());
         mNr = in.readParcelable(CellSignalStrengthLte.class.getClassLoader());
-        mTimestamp = in.readLong();
+        mTimestampMillis = in.readLong();
     }
 
     /**
@@ -331,15 +331,17 @@
         out.writeParcelable(mTdscdma, flags);
         out.writeParcelable(mLte, flags);
         out.writeParcelable(mNr, flags);
-        out.writeLong(mTimestamp);
+        out.writeLong(mTimestampMillis);
     }
 
     /**
-     * @return mTimestamp in nanoseconds
+     * @return timestamp in milliseconds since boot for {@link SignalStrength}.
+     * This timestamp reports the approximate time that the signal was measured and reported
+     * by the modem. It can be used to compare the recency of {@link SignalStrength} instances.
      */
-    @SuppressLint("MethodNameUnits")
-    public long getTimestampNanos() {
-        return mTimestamp;
+    @ElapsedRealtimeLong
+    public long getTimestampMillis() {
+        return mTimestampMillis;
     }
 
    /**
diff --git a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
index 20d0e96..ae20cae 100644
--- a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
+++ b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
@@ -430,10 +430,9 @@
     private void verifyInstalledFiles(String... filenames) throws DeviceNotAvailableException {
         String apkPath = getApkPath(TARGET_PACKAGE);
         String appDir = apkPath.substring(0, apkPath.lastIndexOf("/"));
+        // Exclude directories since we only care about files.
         HashSet<String> actualFiles = new HashSet<>(Arrays.asList(
-                expectRemoteCommandToSucceed("ls " + appDir).split("\n")));
-        assertTrue(actualFiles.remove("lib"));
-        assertTrue(actualFiles.remove("oat"));
+                expectRemoteCommandToSucceed("ls -p " + appDir + " | grep -v '/'").split("\n")));
 
         HashSet<String> expectedFiles = new HashSet<>(Arrays.asList(filenames));
         assertEquals(expectedFiles, actualFiles);
diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp
index 89005da..24623fb 100644
--- a/tests/RollbackTest/Android.bp
+++ b/tests/RollbackTest/Android.bp
@@ -29,7 +29,7 @@
     name: "StagedRollbackTest",
     srcs: ["StagedRollbackTest/src/**/*.java"],
     libs: ["tradefed"],
-    static_libs: ["testng", "compatibility-tradefed"],
+    static_libs: ["testng", "compatibility-tradefed", "RollbackTestLib"],
     test_suites: ["general-tests"],
     test_config: "StagedRollbackTest.xml",
     data: [":com.android.apex.apkrollback.test_v1"],
@@ -39,7 +39,7 @@
     name: "NetworkStagedRollbackTest",
     srcs: ["NetworkStagedRollbackTest/src/**/*.java"],
     libs: ["tradefed"],
-    static_libs: ["testng"],
+    static_libs: ["testng", "RollbackTestLib"],
     test_suites: ["general-tests"],
     test_config: "NetworkStagedRollbackTest.xml",
 }
@@ -52,6 +52,12 @@
     test_config: "MultiUserRollbackTest.xml",
 }
 
+java_library_host {
+    name: "RollbackTestLib",
+    srcs: ["lib/src/**/*.java"],
+    libs: ["tradefed"],
+}
+
 genrule {
   name: "com.android.apex.apkrollback.test.pem",
   out: ["com.android.apex.apkrollback.test.pem"],
diff --git a/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java b/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java
index a72bd38..d4e34f9 100644
--- a/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java
+++ b/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java
@@ -16,12 +16,12 @@
 
 package com.android.tests.rollback.host;
 
+import static com.android.tests.rollback.host.WatchdogEventLogger.watchdogEventOccurred;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.testng.Assert.assertThrows;
 
-import com.android.tradefed.device.LogcatReceiver;
-import com.android.tradefed.result.InputStreamSource;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
@@ -30,10 +30,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.io.BufferedReader;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -58,19 +54,16 @@
     private static final String ROLLBACK_INITIATE = "ROLLBACK_INITIATE";
     private static final String ROLLBACK_BOOT_TRIGGERED = "ROLLBACK_BOOT_TRIGGERED";
 
-    private LogcatReceiver mReceiver;
+    private WatchdogEventLogger mLogger = new WatchdogEventLogger();
 
     @Before
     public void setUp() throws Exception {
-        mReceiver =  new LogcatReceiver(getDevice(), "logcat -s WatchdogRollbackLogger",
-                getDevice().getOptions().getMaxLogcatDataSize(), 0);
-        mReceiver.start();
+        mLogger.start(getDevice());
     }
 
     @After
     public void tearDown() throws Exception {
-        mReceiver.stop();
-        mReceiver.clear();
+        mLogger.stop();
     }
 
     /**
@@ -94,16 +87,12 @@
             getDevice().waitForDeviceAvailable();
             // Verify rollback was executed after health check deadline
             runPhase("testNetworkFailedRollback_Phase4");
-            InputStreamSource logcatStream = mReceiver.getLogcatData();
-            try {
-                List<String> watchdogEvents = getWatchdogLoggingEvents(logcatStream);
-                assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null,
-                        REASON_EXPLICIT_HEALTH_CHECK, null));
-                assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null,
-                        null, null));
-            } finally {
-                logcatStream.close();
-            }
+
+            List<String> watchdogEvents = mLogger.getWatchdogLoggingEvents();
+            assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null,
+                    REASON_EXPLICIT_HEALTH_CHECK, null));
+            assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null,
+                    null, null));
         } finally {
             // Reconnect internet again so we won't break tests which assume internet available
             getDevice().executeShellCommand("svc wifi enable");
@@ -133,66 +122,9 @@
 
         // Verify rollback was not executed after health check deadline
         runPhase("testNetworkPassedDoesNotRollback_Phase3");
-        InputStreamSource logcatStream = mReceiver.getLogcatData();
-        try {
-            List<String> watchdogEvents = getWatchdogLoggingEvents(logcatStream);
-            assertEquals(watchdogEventOccurred(watchdogEvents, null, null,
-                    REASON_EXPLICIT_HEALTH_CHECK, null), false);
-        } finally {
-            logcatStream.close();
-        }
-    }
 
-    /**
-     * Returns a list of all Watchdog logging events which have occurred.
-     */
-    private List<String> getWatchdogLoggingEvents(InputStreamSource inputStreamSource)
-            throws Exception {
-        List<String> watchdogEvents = new ArrayList<>();
-        InputStream inputStream = inputStreamSource.createInputStream();
-        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
-        String line;
-        while ((line = reader.readLine()) != null) {
-            if (line.contains("Watchdog event occurred")) {
-                watchdogEvents.add(line);
-            }
-        }
-        return watchdogEvents;
-    }
-
-    /**
-     * Returns whether a Watchdog event has occurred that matches the given criteria.
-     *
-     * Check the value of all non-null parameters against the list of Watchdog events that have
-     * occurred, and return {@code true} if an event exists which matches all criteria.
-     */
-    private boolean watchdogEventOccurred(List<String> loggingEvents,
-            String type, String logPackage,
-            String rollbackReason, String failedPackageName) throws Exception {
-        List<String> eventCriteria = new ArrayList<>();
-        if (type != null) {
-            eventCriteria.add("type: " + type);
-        }
-        if (logPackage != null) {
-            eventCriteria.add("logPackage: " + logPackage);
-        }
-        if (rollbackReason != null) {
-            eventCriteria.add("rollbackReason: " + rollbackReason);
-        }
-        if (failedPackageName != null) {
-            eventCriteria.add("failedPackageName: " + failedPackageName);
-        }
-        for (String loggingEvent: loggingEvents) {
-            boolean matchesCriteria = true;
-            for (String criterion: eventCriteria) {
-                if (!loggingEvent.contains(criterion)) {
-                    matchesCriteria = false;
-                }
-            }
-            if (matchesCriteria) {
-                return true;
-            }
-        }
-        return false;
+        List<String> watchdogEvents = mLogger.getWatchdogLoggingEvents();
+        assertEquals(watchdogEventOccurred(watchdogEvents, null, null,
+                REASON_EXPLICIT_HEALTH_CHECK, null), false);
     }
 }
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 c3fd962..8104d1d 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
@@ -16,6 +16,8 @@
 
 package com.android.tests.rollback.host;
 
+import static com.android.tests.rollback.host.WatchdogEventLogger.watchdogEventOccurred;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -23,8 +25,6 @@
 import static org.testng.Assert.assertThrows;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.tradefed.device.LogcatReceiver;
-import com.android.tradefed.result.InputStreamSource;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
@@ -33,11 +33,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.io.BufferedReader;
 import java.io.File;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
@@ -83,12 +79,10 @@
     private static final String ROLLBACK_INITIATE = "ROLLBACK_INITIATE";
     private static final String ROLLBACK_BOOT_TRIGGERED = "ROLLBACK_BOOT_TRIGGERED";
 
-    private LogcatReceiver mReceiver;
+    private WatchdogEventLogger mLogger = new WatchdogEventLogger();
 
     @Before
     public void setUp() throws Exception {
-        mReceiver =  new LogcatReceiver(getDevice(), "logcat -s WatchdogRollbackLogger",
-                getDevice().getOptions().getMaxLogcatDataSize(), 0);
         if (!getDevice().isAdbRoot()) {
             getDevice().enableAdbRoot();
         }
@@ -98,13 +92,12 @@
                         + "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex");
         getDevice().reboot();
         runPhase("testCleanUp");
-        mReceiver.start();
+        mLogger.start(getDevice());
     }
 
     @After
     public void tearDown() throws Exception {
-        mReceiver.stop();
-        mReceiver.clear();
+        mLogger.stop();
         runPhase("testCleanUp");
 
         if (!getDevice().isAdbRoot()) {
@@ -134,16 +127,12 @@
         getDevice().waitForDeviceAvailable();
 
         runPhase("testBadApkOnly_Phase4");
-        InputStreamSource logcatStream = mReceiver.getLogcatData();
-        try {
-            List<String> watchdogEvents = getWatchdogLoggingEvents(logcatStream);
-            assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null,
-                    REASON_APP_CRASH, TESTAPP_A));
-            assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null,
-                    null, null));
-        } finally {
-            logcatStream.close();
-        }
+
+        List<String> watchdogEvents = mLogger.getWatchdogLoggingEvents();
+        assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null,
+                REASON_APP_CRASH, TESTAPP_A));
+        assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null,
+                null, null));
     }
 
     @Test
@@ -171,16 +160,12 @@
 
         // verify rollback committed
         runPhase("testNativeWatchdogTriggersRollback_Phase3");
-        InputStreamSource logcatStream = mReceiver.getLogcatData();
-        try {
-            List<String> watchdogEvents = getWatchdogLoggingEvents(logcatStream);
-            assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null,
-                            REASON_NATIVE_CRASH, null));
-            assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null,
-                    null, null));
-        } finally {
-            logcatStream.close();
-        }
+
+        List<String> watchdogEvents = mLogger.getWatchdogLoggingEvents();
+        assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null,
+                        REASON_NATIVE_CRASH, null));
+        assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null,
+                null, null));
     }
 
     @Test
@@ -215,16 +200,12 @@
 
         // verify all available rollbacks have been committed
         runPhase("testNativeWatchdogTriggersRollbackForAll_Phase4");
-        InputStreamSource logcatStream = mReceiver.getLogcatData();
-        try {
-            List<String> watchdogEvents = getWatchdogLoggingEvents(logcatStream);
-            assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null,
-                            REASON_NATIVE_CRASH, null));
-            assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null,
-                    null, null));
-        } finally {
-            logcatStream.close();
-        }
+
+        List<String> watchdogEvents = mLogger.getWatchdogLoggingEvents();
+        assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null,
+                        REASON_NATIVE_CRASH, null));
+        assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null,
+                null, null));
     }
 
     /**
@@ -290,16 +271,12 @@
         getDevice().waitForDeviceAvailable();
         // Verify rollback occurred due to crash of apk-in-apex
         runPhase("testRollbackApexWithApkCrashing_Phase3");
-        InputStreamSource logcatStream = mReceiver.getLogcatData();
-        try {
-            List<String> watchdogEvents = getWatchdogLoggingEvents(logcatStream);
-            assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null,
-                    REASON_APP_CRASH, TESTAPP_A));
-            assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null,
-                    null, null));
-        } finally {
-            logcatStream.close();
-        }
+
+        List<String> watchdogEvents = mLogger.getWatchdogLoggingEvents();
+        assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null,
+                REASON_APP_CRASH, TESTAPP_A));
+        assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null,
+                null, null));
     }
 
     /**
@@ -464,57 +441,4 @@
             return false;
         }
     }
-
-    /**
-     * Returns a list of all Watchdog logging events which have occurred.
-     */
-    private List<String> getWatchdogLoggingEvents(InputStreamSource inputStreamSource)
-            throws Exception {
-        List<String> watchdogEvents = new ArrayList<>();
-        InputStream inputStream = inputStreamSource.createInputStream();
-        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
-        String line;
-        while ((line = reader.readLine()) != null) {
-            if (line.contains("Watchdog event occurred")) {
-                watchdogEvents.add(line);
-            }
-        }
-        return watchdogEvents;
-    }
-
-    /**
-     * Returns whether a Watchdog event has occurred that matches the given criteria.
-     *
-     * Check the value of all non-null parameters against the list of Watchdog events that have
-     * occurred, and return {@code true} if an event exists which matches all criteria.
-     */
-    private boolean watchdogEventOccurred(List<String> loggingEvents,
-            String type, String logPackage,
-            String rollbackReason, String failedPackageName) throws Exception {
-        List<String> eventCriteria = new ArrayList<>();
-        if (type != null) {
-            eventCriteria.add("type: " + type);
-        }
-        if (logPackage != null) {
-            eventCriteria.add("logPackage: " + logPackage);
-        }
-        if (rollbackReason != null) {
-            eventCriteria.add("rollbackReason: " + rollbackReason);
-        }
-        if (failedPackageName != null) {
-            eventCriteria.add("failedPackageName: " + failedPackageName);
-        }
-        for (String loggingEvent: loggingEvents) {
-            boolean matchesCriteria = true;
-            for (String criterion: eventCriteria) {
-                if (!loggingEvent.contains(criterion)) {
-                    matchesCriteria = false;
-                }
-            }
-            if (matchesCriteria) {
-                return true;
-            }
-        }
-        return false;
-    }
 }
diff --git a/tests/RollbackTest/lib/src/com/android/tests/rollback/host/WatchdogEventLogger.java b/tests/RollbackTest/lib/src/com/android/tests/rollback/host/WatchdogEventLogger.java
new file mode 100644
index 0000000..317d67e
--- /dev/null
+++ b/tests/RollbackTest/lib/src/com/android/tests/rollback/host/WatchdogEventLogger.java
@@ -0,0 +1,101 @@
+/*
+ * 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.tests.rollback.host;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.device.LogcatReceiver;
+import com.android.tradefed.result.InputStreamSource;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+
+public class WatchdogEventLogger {
+    private LogcatReceiver mReceiver;
+
+    public void start(ITestDevice device) {
+        mReceiver =  new LogcatReceiver(device, "logcat -s WatchdogRollbackLogger",
+                device.getOptions().getMaxLogcatDataSize(), 0);
+        mReceiver.start();
+    }
+
+    public void stop() {
+        mReceiver.stop();
+        mReceiver.clear();
+    }
+
+    /**
+     * Returns a list of all Watchdog logging events which have occurred.
+     */
+    public List<String> getWatchdogLoggingEvents() throws Exception {
+        try (InputStreamSource logcatStream = mReceiver.getLogcatData()) {
+            return getWatchdogLoggingEvents(logcatStream);
+        }
+    }
+
+    private static List<String> getWatchdogLoggingEvents(InputStreamSource inputStreamSource)
+            throws Exception {
+        List<String> watchdogEvents = new ArrayList<>();
+        InputStream inputStream = inputStreamSource.createInputStream();
+        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
+        String line;
+        while ((line = reader.readLine()) != null) {
+            if (line.contains("Watchdog event occurred")) {
+                watchdogEvents.add(line);
+            }
+        }
+        return watchdogEvents;
+    }
+
+    /**
+     * Returns whether a Watchdog event has occurred that matches the given criteria.
+     *
+     * Check the value of all non-null parameters against the list of Watchdog events that have
+     * occurred, and return {@code true} if an event exists which matches all criteria.
+     */
+    public static boolean watchdogEventOccurred(List<String> loggingEvents,
+            String type, String logPackage,
+            String rollbackReason, String failedPackageName) throws Exception {
+        List<String> eventCriteria = new ArrayList<>();
+        if (type != null) {
+            eventCriteria.add("type: " + type);
+        }
+        if (logPackage != null) {
+            eventCriteria.add("logPackage: " + logPackage);
+        }
+        if (rollbackReason != null) {
+            eventCriteria.add("rollbackReason: " + rollbackReason);
+        }
+        if (failedPackageName != null) {
+            eventCriteria.add("failedPackageName: " + failedPackageName);
+        }
+        for (String loggingEvent: loggingEvents) {
+            boolean matchesCriteria = true;
+            for (String criterion: eventCriteria) {
+                if (!loggingEvent.contains(criterion)) {
+                    matchesCriteria = false;
+                }
+            }
+            if (matchesCriteria) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index 155c61f..eb78529 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -148,6 +148,7 @@
     @Mock private AppOpsManager mAppOps;
     @Mock private NotificationManager mNotificationManager;
     @Mock private Vpn.SystemServices mSystemServices;
+    @Mock private Vpn.Ikev2SessionCreator mIkev2SessionCreator;
     @Mock private ConnectivityManager mConnectivityManager;
     @Mock private KeyStore mKeyStore;
     private final VpnProfile mVpnProfile = new VpnProfile("key");
@@ -867,7 +868,8 @@
      * Mock some methods of vpn object.
      */
     private Vpn createVpn(@UserIdInt int userId) {
-        return new Vpn(Looper.myLooper(), mContext, mNetService, userId, mSystemServices);
+        return new Vpn(Looper.myLooper(), mContext, mNetService,
+                userId, mSystemServices, mIkev2SessionCreator);
     }
 
     private static void assertBlocked(Vpn vpn, int... uids) {
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index b6f4490..f693315 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -28,6 +28,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.app.ActivityManager;
@@ -1621,11 +1622,13 @@
          * @param wifiConfiguration WifiConfiguration object corresponding to the network
          *                          user selected.
          */
+        @SuppressLint("CallbackMethodName")
         default void select(@NonNull WifiConfiguration wifiConfiguration) {}
 
         /**
          * User rejected the app's request.
          */
+        @SuppressLint("CallbackMethodName")
         default void reject() {}
     }