Merge "Add additional constraint in DPMS for Automotive" into sc-dev
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/WindowPerfRunPreconditionBase.java b/apct-tests/perftests/utils/src/android/perftests/utils/WindowPerfRunPreconditionBase.java
index 8d2ac02..330a19e0 100644
--- a/apct-tests/perftests/utils/src/android/perftests/utils/WindowPerfRunPreconditionBase.java
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/WindowPerfRunPreconditionBase.java
@@ -89,7 +89,7 @@
                         navOverlay = WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY;
                         break;
                 }
-                executeShellCommand("cmd overlay enable-exclusive " + navOverlay);
+                executeShellCommand("cmd overlay enable-exclusive --category " + navOverlay);
             });
 
     /** It only executes once before all tests. */
diff --git a/apex/jobscheduler/framework/Android.bp b/apex/jobscheduler/framework/Android.bp
index 6650e67..fd35537 100644
--- a/apex/jobscheduler/framework/Android.bp
+++ b/apex/jobscheduler/framework/Android.bp
@@ -22,6 +22,7 @@
         ],
     },
     libs: [
+        "app-compat-annotations",
         "framework-minus-apex",
         "unsupportedappusage",
     ],
diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
index 7851087..7c7b210 100644
--- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java
+++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
@@ -16,11 +16,14 @@
 
 package android.app;
 
+import android.Manifest;
 import android.annotation.IntDef;
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.Intent;
@@ -183,6 +186,25 @@
     @UnsupportedAppUsage
     public static final int FLAG_IDLE_UNTIL = 1<<4;
 
+    /**
+     * Flag for alarms: Used to provide backwards compatibility for apps with targetSdkVersion less
+     * than {@link Build.VERSION_CODES#S}
+     * @hide
+     */
+    public static final int FLAG_ALLOW_WHILE_IDLE_COMPAT = 1 << 5;
+
+    /**
+     * For apps targeting {@link Build.VERSION_CODES#S} or above, APIs
+     * {@link #setExactAndAllowWhileIdle(int, long, PendingIntent)} and
+     * {@link #setAlarmClock(AlarmClockInfo, PendingIntent)} will require holding a new
+     * permission {@link android.Manifest.permission#SCHEDULE_EXACT_ALARM}
+     *
+     * @hide
+     */
+    @ChangeId
+    @Disabled // TODO (b/171306433): Enable starting S.
+    public static final long REQUIRE_EXACT_ALARM_PERMISSION = 171306433L;
+
     @UnsupportedAppUsage
     private final IAlarmManager mService;
     private final Context mContext;
@@ -588,6 +610,11 @@
      * This method is like {@link #setExact(int, long, PendingIntent)}, but implies
      * {@link #RTC_WAKEUP}.
      *
+     * <p>
+     * Starting from API {@link Build.VERSION_CODES#S}, using this method requires the
+     * {@link Manifest.permission#SCHEDULE_EXACT_ALARM} permission. Alarms scheduled via this API
+     * will be allowed to start a foreground service even if the app is in the background.
+     *
      * @param info
      * @param operation Action to perform when the alarm goes off;
      *        typically comes from {@link PendingIntent#getBroadcast
@@ -603,6 +630,7 @@
      * @see android.content.Context#registerReceiver
      * @see android.content.Intent#filterEquals
      */
+    @RequiresPermission(Manifest.permission.SCHEDULE_EXACT_ALARM)
     public void setAlarmClock(AlarmClockInfo info, PendingIntent operation) {
         setImpl(RTC_WAKEUP, info.getTriggerTime(), WINDOW_EXACT, 0, 0, operation,
                 null, null, null, null, info);
@@ -876,6 +904,12 @@
      * device is idle it may take even more liberties with scheduling in order to optimize
      * for battery life.</p>
      *
+     * <p>
+     * Starting from API {@link Build.VERSION_CODES#S}, using this method requires the
+     * {@link Manifest.permission#SCHEDULE_EXACT_ALARM} permission, unless the app is exempt from
+     * battery restrictions. Alarms scheduled via this API will be allowed to start a foreground
+     * service even if the app is in the background.
+     *
      * @param type type of alarm.
      * @param triggerAtMillis time in milliseconds that the alarm should go
      *        off, using the appropriate clock (depending on the alarm type).
@@ -895,6 +929,7 @@
      * @see #RTC
      * @see #RTC_WAKEUP
      */
+    @RequiresPermission(value = Manifest.permission.SCHEDULE_EXACT_ALARM, conditional = true)
     public void setExactAndAllowWhileIdle(@AlarmType int type, long triggerAtMillis,
             PendingIntent operation) {
         setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, operation,
@@ -1018,6 +1053,18 @@
     }
 
     /**
+     * Called to check if the caller has permission to use alarms set via {@link }
+     * @return
+     */
+    public boolean canScheduleExactAlarms() {
+        try {
+            return mService.canScheduleExactAlarms();
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Gets information about the next alarm clock currently scheduled.
      *
      * The alarm clocks considered are those scheduled by any application
diff --git a/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl b/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl
index 2c51935..2f21ce3 100644
--- a/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl
+++ b/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl
@@ -41,4 +41,5 @@
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     AlarmManager.AlarmClockInfo getNextAlarmClock(int userId);
     long currentNetworkTimeMillis();
+    boolean canScheduleExactAlarms();
 }
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java b/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java
index 657c368..3bc7b30 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java
@@ -26,6 +26,7 @@
 import android.app.AlarmManager;
 import android.app.IAlarmListener;
 import android.app.PendingIntent;
+import android.os.Bundle;
 import android.os.WorkSource;
 import android.util.IndentingPrintWriter;
 import android.util.TimeUtils;
@@ -92,10 +93,12 @@
     private long mWhenElapsed;
     private long mMaxWhenElapsed;
     public AlarmManagerService.PriorityClass priorityClass;
+    /** Broadcast options to use when delivering this alarm */
+    public Bundle mIdleOptions;
 
     Alarm(int type, long when, long requestedWhenElapsed, long windowLength, long interval,
             PendingIntent op, IAlarmListener rec, String listenerTag, WorkSource ws, int flags,
-            AlarmManager.AlarmClockInfo info, int uid, String pkgName) {
+            AlarmManager.AlarmClockInfo info, int uid, String pkgName, Bundle idleOptions) {
         this.type = type;
         origWhen = when;
         wakeup = type == AlarmManager.ELAPSED_REALTIME_WAKEUP
@@ -115,6 +118,7 @@
         alarmClock = info;
         this.uid = uid;
         packageName = pkgName;
+        mIdleOptions = idleOptions;
         sourcePackage = (operation != null) ? operation.getCreatorPackage() : packageName;
         creatorUid = (operation != null) ? operation.getCreatorUid() : this.uid;
     }
@@ -303,6 +307,10 @@
             ipw.print("listener=");
             ipw.println(listener.asBinder());
         }
+        if (mIdleOptions != null) {
+            ipw.print("idle-options=");
+            ipw.println(mIdleOptions.toString());
+        }
     }
 
     public void dumpDebug(ProtoOutputStream proto, long fieldId, long nowElapsed) {
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index f6a1b8a..559a434 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -20,11 +20,15 @@
 import static android.app.AlarmManager.ELAPSED_REALTIME;
 import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
 import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE;
+import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_COMPAT;
 import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
 import static android.app.AlarmManager.FLAG_IDLE_UNTIL;
+import static android.app.AlarmManager.FLAG_WAKE_FROM_IDLE;
+import static android.app.AlarmManager.INTERVAL_HOUR;
 import static android.app.AlarmManager.RTC;
 import static android.app.AlarmManager.RTC_WAKEUP;
 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED;
 import static android.os.UserHandle.USER_SYSTEM;
 
 import static com.android.server.alarm.Alarm.APP_STANDBY_POLICY_INDEX;
@@ -32,6 +36,7 @@
 import static com.android.server.alarm.Alarm.DEVICE_IDLE_POLICY_INDEX;
 import static com.android.server.alarm.Alarm.REQUESTER_POLICY_INDEX;
 
+import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.app.Activity;
@@ -43,12 +48,14 @@
 import android.app.IAlarmListener;
 import android.app.IAlarmManager;
 import android.app.PendingIntent;
+import android.app.compat.CompatChanges;
 import android.app.usage.UsageStatsManager;
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.PermissionChecker;
 import android.content.pm.PackageManagerInternal;
 import android.net.Uri;
 import android.os.BatteryManager;
@@ -65,6 +72,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
+import android.os.ServiceManager;
 import android.os.ShellCallback;
 import android.os.ShellCommand;
 import android.os.SystemClock;
@@ -95,6 +103,8 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IAppOpsCallback;
+import com.android.internal.app.IAppOpsService;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.LocalLog;
@@ -177,6 +187,7 @@
     final LocalLog mLog = new LocalLog(TAG);
 
     AppOpsManager mAppOps;
+    IAppOpsService mAppOpsService;
     DeviceIdleInternal mLocalDeviceIdleController;
     private UsageStatsManagerInternal mUsageStatsManagerInternal;
     private ActivityManagerInternal mActivityManagerInternal;
@@ -253,10 +264,8 @@
             "REORDER_ALARMS_FOR_STANDBY",
     });
 
-    /**
-     * Broadcast options to use for FLAG_ALLOW_WHILE_IDLE.
-     */
-    Bundle mIdleOptions;
+    BroadcastOptions mOptsWithFgs = BroadcastOptions.makeBasic();
+    BroadcastOptions mOptsWithoutFgs = BroadcastOptions.makeBasic();
 
     // TODO(b/172085676): Move inside alarm store.
     private final SparseArray<AlarmManager.AlarmClockInfo> mNextAlarmClockForUser =
@@ -410,6 +419,10 @@
         @VisibleForTesting
         static final String KEY_ALLOW_WHILE_IDLE_QUOTA = "allow_while_idle_quota";
 
+        @VisibleForTesting
+        static final String KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA = "allow_while_idle_compat_quota";
+        private static final String KEY_ALLOW_WHILE_IDLE_WINDOW = "allow_while_idle_window";
+
         private static final long DEFAULT_MIN_FUTURITY = 5 * 1000;
         private static final long DEFAULT_MIN_INTERVAL = 60 * 1000;
         private static final long DEFAULT_MAX_INTERVAL = 365 * DateUtils.DAY_IN_MILLIS;
@@ -433,8 +446,14 @@
         private static final boolean DEFAULT_LAZY_BATCHING = true;
         private static final boolean DEFAULT_TIME_TICK_ALLOWED_WHILE_IDLE = true;
 
-        private static final int DEFAULT_ALLOW_WHILE_IDLE_QUOTA = 7;
-        public static final long ALLOW_WHILE_IDLE_WINDOW = 60 * 60 * 1000; // 1 hour.
+        /**
+         * Default quota for pre-S apps. Enough to accommodate the existing policy of an alarm
+         * every ALLOW_WHILE_IDLE_LONG_DELAY, which was 9 minutes.
+         */
+        private static final int DEFAULT_ALLOW_WHILE_IDLE_COMPAT_QUOTA = 7;
+        private static final int DEFAULT_ALLOW_WHILE_IDLE_QUOTA = 72;
+
+        private static final long DEFAULT_ALLOW_WHILE_IDLE_WINDOW = 60 * 60 * 1000; // 1 hour.
 
         // Minimum futurity of a new alarm
         public long MIN_FUTURITY = DEFAULT_MIN_FUTURITY;
@@ -463,6 +482,19 @@
 
         public int ALLOW_WHILE_IDLE_QUOTA = DEFAULT_ALLOW_WHILE_IDLE_QUOTA;
 
+        /**
+         * Used to provide backwards compatibility to pre-S apps with a quota equivalent to the
+         * earlier delay throttling mechanism.
+         */
+        public int ALLOW_WHILE_IDLE_COMPAT_QUOTA = DEFAULT_ALLOW_WHILE_IDLE_COMPAT_QUOTA;
+
+        /**
+         * The window used for enforcing {@link #ALLOW_WHILE_IDLE_QUOTA} and
+         * {@link #ALLOW_WHILE_IDLE_COMPAT_QUOTA}. Can be configured, but only recommended for
+         * testing.
+         */
+        public long ALLOW_WHILE_IDLE_WINDOW = DEFAULT_ALLOW_WHILE_IDLE_WINDOW;
+
         private long mLastAllowWhileIdleWhitelistDuration = -1;
 
         Constants() {
@@ -480,9 +512,11 @@
         public void updateAllowWhileIdleWhitelistDurationLocked() {
             if (mLastAllowWhileIdleWhitelistDuration != ALLOW_WHILE_IDLE_WHITELIST_DURATION) {
                 mLastAllowWhileIdleWhitelistDuration = ALLOW_WHILE_IDLE_WHITELIST_DURATION;
-                BroadcastOptions opts = BroadcastOptions.makeBasic();
-                opts.setTemporaryAppWhitelistDuration(ALLOW_WHILE_IDLE_WHITELIST_DURATION);
-                mIdleOptions = opts.toBundle();
+
+                mOptsWithFgs.setTemporaryAppWhitelistDuration(ALLOW_WHILE_IDLE_WHITELIST_DURATION);
+                mOptsWithoutFgs.setTemporaryAppWhitelistDuration(
+                        TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED,
+                        ALLOW_WHILE_IDLE_WHITELIST_DURATION);
             }
         }
 
@@ -516,6 +550,27 @@
                                 ALLOW_WHILE_IDLE_QUOTA = 1;
                             }
                             break;
+                        case KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA:
+                            ALLOW_WHILE_IDLE_COMPAT_QUOTA = properties.getInt(
+                                    KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA,
+                                    DEFAULT_ALLOW_WHILE_IDLE_COMPAT_QUOTA);
+                            if (ALLOW_WHILE_IDLE_COMPAT_QUOTA <= 0) {
+                                Slog.w(TAG, "Cannot have quota lower than 1.");
+                                ALLOW_WHILE_IDLE_COMPAT_QUOTA = 1;
+                            }
+                            break;
+                        case KEY_ALLOW_WHILE_IDLE_WINDOW:
+                            ALLOW_WHILE_IDLE_WINDOW = properties.getLong(
+                                    KEY_ALLOW_WHILE_IDLE_WINDOW, DEFAULT_ALLOW_WHILE_IDLE_WINDOW);
+                            if (ALLOW_WHILE_IDLE_WINDOW > DEFAULT_ALLOW_WHILE_IDLE_WINDOW) {
+                                Slog.w(TAG, "Cannot have allow_while_idle_window > "
+                                        + DEFAULT_ALLOW_WHILE_IDLE_WINDOW);
+                                ALLOW_WHILE_IDLE_WINDOW = DEFAULT_ALLOW_WHILE_IDLE_WINDOW;
+                            } else if (ALLOW_WHILE_IDLE_WINDOW < DEFAULT_ALLOW_WHILE_IDLE_WINDOW) {
+                                Slog.w(TAG, "Using a non-default allow_while_idle_window = "
+                                        + ALLOW_WHILE_IDLE_WINDOW);
+                            }
+                            break;
                         case KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION:
                             ALLOW_WHILE_IDLE_WHITELIST_DURATION = properties.getLong(
                                     KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION,
@@ -643,10 +698,14 @@
             TimeUtils.formatDuration(LISTENER_TIMEOUT, pw);
             pw.println();
 
-            pw.print("allow_while_idle_window=");
+            pw.print(KEY_ALLOW_WHILE_IDLE_WINDOW);
+            pw.print("=");
             TimeUtils.formatDuration(ALLOW_WHILE_IDLE_WINDOW, pw);
             pw.println();
 
+            pw.print(KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA, ALLOW_WHILE_IDLE_COMPAT_QUOTA);
+            pw.println();
+
             pw.print(KEY_ALLOW_WHILE_IDLE_QUOTA, ALLOW_WHILE_IDLE_QUOTA);
             pw.println();
 
@@ -830,7 +889,15 @@
         if (futurity < MIN_FUZZABLE_INTERVAL) {
             futurity = 0;
         }
-        return clampPositive(triggerAtTime + (long) (.75 * futurity));
+        long maxElapsed = triggerAtTime + (long) (0.75 * futurity);
+        // For non-repeating alarms, window is capped at a maximum of one hour from the requested
+        // delivery time. This allows for inexact-while-idle alarms to be slightly more reliable.
+        // In practice, the delivery window should generally be much smaller than that
+        // when the device is not idling.
+        if (interval == 0) {
+            maxElapsed = Math.min(maxElapsed, triggerAtTime + INTERVAL_HOUR);
+        }
+        return clampPositive(maxElapsed);
     }
 
     // The RTC clock has moved arbitrarily, so we need to recalculate all the RTC alarm deliveries.
@@ -998,7 +1065,7 @@
                 setImplLocked(alarm.type, alarm.origWhen + delta, nextElapsed,
                         nextMaxElapsed - nextElapsed, alarm.repeatInterval, alarm.operation, null,
                         null, alarm.flags, alarm.workSource, alarm.alarmClock, alarm.uid,
-                        alarm.packageName);
+                        alarm.packageName, null);
                 // Kernel alarms will be rescheduled as needed in setImplLocked
             }
         }
@@ -1241,7 +1308,8 @@
             mAlarmStore.setAlarmClockRemovalListener(mAlarmClockUpdater);
 
             mAppWakeupHistory = new AppWakeupHistory(Constants.DEFAULT_APP_STANDBY_WINDOW);
-            mAllowWhileIdleHistory = new AppWakeupHistory(Constants.ALLOW_WHILE_IDLE_WINDOW);
+            mAllowWhileIdleHistory = new AppWakeupHistory(
+                    Constants.DEFAULT_ALLOW_WHILE_IDLE_WINDOW);
 
             mNextWakeup = mNextNonWakeup = 0;
 
@@ -1327,6 +1395,28 @@
             synchronized (mLock) {
                 mConstants.start();
                 mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
+                mAppOpsService = mInjector.getAppOpsService();
+                try {
+                    mAppOpsService.startWatchingMode(AppOpsManager.OP_SCHEDULE_EXACT_ALARM, null,
+                            new IAppOpsCallback.Stub() {
+                                @Override
+                                public void opChanged(int op, int uid, String packageName)
+                                        throws RemoteException {
+                                    if (op != AppOpsManager.OP_SCHEDULE_EXACT_ALARM) {
+                                        return;
+                                    }
+                                    final int mode = mAppOpsService.checkOperation(op, uid,
+                                            packageName);
+                                    if (mode != AppOpsManager.MODE_ALLOWED
+                                            && mode != AppOpsManager.MODE_DEFAULT) {
+                                        mHandler.obtainMessage(AlarmHandler.REMOVE_EXACT_ALARMS,
+                                                uid, 0, packageName).sendToTarget();
+                                    }
+                                }
+                            });
+                } catch (RemoteException e) {
+                }
+
                 mLocalDeviceIdleController =
                         LocalServices.getService(DeviceIdleInternal.class);
                 mUsageStatsManagerInternal =
@@ -1428,7 +1518,7 @@
     void setImpl(int type, long triggerAtTime, long windowLength, long interval,
             PendingIntent operation, IAlarmListener directReceiver, String listenerTag,
             int flags, WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock,
-            int callingUid, String callingPackage) {
+            int callingUid, String callingPackage, Bundle idleOptions) {
         if ((operation == null && directReceiver == null)
                 || (operation != null && directReceiver != null)) {
             Slog.w(TAG, "Alarms must either supply a PendingIntent or an AlarmReceiver");
@@ -1519,17 +1609,18 @@
             }
             setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, interval, operation,
                     directReceiver, listenerTag, flags, workSource, alarmClock, callingUid,
-                    callingPackage);
+                    callingPackage, idleOptions);
         }
     }
 
     private void setImplLocked(int type, long when, long whenElapsed, long windowLength,
             long interval, PendingIntent operation, IAlarmListener directReceiver,
             String listenerTag, int flags, WorkSource workSource,
-            AlarmManager.AlarmClockInfo alarmClock, int callingUid, String callingPackage) {
+            AlarmManager.AlarmClockInfo alarmClock, int callingUid, String callingPackage,
+            Bundle idleOptions) {
         final Alarm a = new Alarm(type, when, whenElapsed, windowLength, interval,
                 operation, directReceiver, listenerTag, workSource, flags, alarmClock,
-                callingUid, callingPackage);
+                callingUid, callingPackage, idleOptions);
         if (mActivityManagerInternal.isAppStartModeDisabled(callingUid, callingPackage)) {
             Slog.w(TAG, "Not setting alarm from " + callingUid + ":" + a
                     + " -- package not allowed to start");
@@ -1605,19 +1696,21 @@
             return false;
         }
 
-        if (!(mAppStateTracker != null && mAppStateTracker.areAlarmsRestrictedByBatterySaver(
-                alarm.creatorUid, alarm.sourcePackage))) {
+        if (mAppStateTracker == null || !mAppStateTracker.areAlarmsRestrictedByBatterySaver(
+                alarm.creatorUid, alarm.sourcePackage)) {
             return alarm.setPolicyElapsed(BATTERY_SAVER_POLICY_INDEX, nowElapsed);
         }
 
         final long batterySaverPolicyElapsed;
-        if ((alarm.flags & (AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED)) != 0) {
+        if ((alarm.flags & (FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED)) != 0) {
             // Unrestricted.
             batterySaverPolicyElapsed = nowElapsed;
-        } else if ((alarm.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0) {
+        } else if (isAllowedWhileIdleRestricted(alarm)) {
             // Allowed but limited.
             final int userId = UserHandle.getUserId(alarm.creatorUid);
-            final int quota = mConstants.ALLOW_WHILE_IDLE_QUOTA;
+            final int quota = ((alarm.flags & FLAG_ALLOW_WHILE_IDLE) != 0)
+                    ? mConstants.ALLOW_WHILE_IDLE_QUOTA
+                    : mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA;
             final int dispatchesInWindow = mAllowWhileIdleHistory.getTotalWakeupsInWindow(
                     alarm.sourcePackage, userId);
             if (dispatchesInWindow < quota) {
@@ -1625,7 +1718,7 @@
                 batterySaverPolicyElapsed = nowElapsed;
             } else {
                 batterySaverPolicyElapsed = mAllowWhileIdleHistory.getNthLastWakeupForPackage(
-                        alarm.sourcePackage, userId, quota) + Constants.ALLOW_WHILE_IDLE_WINDOW;
+                        alarm.sourcePackage, userId, quota) + mConstants.ALLOW_WHILE_IDLE_WINDOW;
             }
         } else {
             // Not allowed.
@@ -1635,6 +1728,16 @@
     }
 
     /**
+     * Returns {@code true} if the given alarm has the flag
+     * {@link AlarmManager#FLAG_ALLOW_WHILE_IDLE} or
+     * {@link AlarmManager#FLAG_ALLOW_WHILE_IDLE_COMPAT}
+     *
+     */
+    private static boolean isAllowedWhileIdleRestricted(Alarm a) {
+        return (a.flags & (FLAG_ALLOW_WHILE_IDLE | FLAG_ALLOW_WHILE_IDLE_COMPAT)) != 0;
+    }
+
+    /**
      * Adjusts the delivery time of the alarm based on device_idle (doze) rules.
      *
      * @param alarm The alarm to adjust
@@ -1647,14 +1750,15 @@
         }
 
         final long deviceIdlePolicyTime;
-        if ((alarm.flags & (AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED
-                | AlarmManager.FLAG_WAKE_FROM_IDLE)) != 0) {
+        if ((alarm.flags & (FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED | FLAG_WAKE_FROM_IDLE)) != 0) {
             // Unrestricted.
             deviceIdlePolicyTime = nowElapsed;
-        } else if ((alarm.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0) {
+        } else if (isAllowedWhileIdleRestricted(alarm)) {
             // Allowed but limited.
             final int userId = UserHandle.getUserId(alarm.creatorUid);
-            final int quota = mConstants.ALLOW_WHILE_IDLE_QUOTA;
+            final int quota = ((alarm.flags & FLAG_ALLOW_WHILE_IDLE) != 0)
+                    ? mConstants.ALLOW_WHILE_IDLE_QUOTA
+                    : mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA;
             final int dispatchesInWindow = mAllowWhileIdleHistory.getTotalWakeupsInWindow(
                     alarm.sourcePackage, userId);
             if (dispatchesInWindow < quota) {
@@ -1662,7 +1766,7 @@
                 deviceIdlePolicyTime = nowElapsed;
             } else {
                 final long whenInQuota = mAllowWhileIdleHistory.getNthLastWakeupForPackage(
-                        alarm.sourcePackage, userId, quota) + Constants.ALLOW_WHILE_IDLE_WINDOW;
+                        alarm.sourcePackage, userId, quota) + mConstants.ALLOW_WHILE_IDLE_WINDOW;
                 deviceIdlePolicyTime = Math.min(whenInQuota, mPendingIdleUntil.getWhenElapsed());
             }
         } else {
@@ -1749,7 +1853,7 @@
         } else if (mPendingIdleUntil != null) {
             adjustDeliveryTimeBasedOnDeviceIdle(a);
         }
-        if ((a.flags & AlarmManager.FLAG_WAKE_FROM_IDLE) != 0) {
+        if ((a.flags & FLAG_WAKE_FROM_IDLE) != 0) {
             if (mNextWakeFromIdle == null || mNextWakeFromIdle.getWhenElapsed()
                     > a.getWhenElapsed()) {
                 mNextWakeFromIdle = a;
@@ -1810,6 +1914,14 @@
     }
 
     /**
+     * Returns true if the given uid is on the system or user's power save exclusion list.
+     */
+    boolean isWhitelisted(int uid) {
+        return (mLocalDeviceIdleController == null || mLocalDeviceIdleController.isAppOnWhitelist(
+                UserHandle.getAppId(uid)));
+    }
+
+    /**
      * Public-facing binder interface
      */
     private final IBinder mService = new IAlarmManager.Stub() {
@@ -1824,6 +1936,54 @@
             // wakelock time spent in alarm delivery
             mAppOps.checkPackage(callingUid, callingPackage);
 
+            final boolean allowWhileIdle = (flags & FLAG_ALLOW_WHILE_IDLE) != 0;
+
+            Bundle idleOptions = null;
+            if (alarmClock != null || allowWhileIdle) {
+                // make sure the caller is allowed to use the requested kind of alarm, and also
+                // decide what broadcast options to use.
+                final boolean needsPermission;
+                boolean lowQuota;
+                if (CompatChanges.isChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION,
+                        callingPackage, UserHandle.getUserHandleForUid(callingUid))) {
+                    if (windowLength != AlarmManager.WINDOW_EXACT) {
+                        needsPermission = false;
+                        lowQuota = true;
+                        idleOptions = isWhitelisted(callingUid) ? mOptsWithFgs.toBundle()
+                                : mOptsWithoutFgs.toBundle();
+                    } else if (alarmClock != null) {
+                        needsPermission = true;
+                        lowQuota = false;
+                        idleOptions = mOptsWithFgs.toBundle();
+                    } else {
+                        needsPermission = true;
+                        lowQuota = false;
+                        idleOptions = mOptsWithFgs.toBundle();
+                    }
+                } else {
+                    needsPermission = false;
+                    lowQuota = allowWhileIdle;
+                    idleOptions = allowWhileIdle ? mOptsWithFgs.toBundle() : null;
+                }
+                if (needsPermission && !canScheduleExactAlarms()) {
+                    if (alarmClock == null && isWhitelisted(callingUid)) {
+                        // If the app is on the full system allow-list (not except-idle), we still
+                        // allow the alarms, but with a lower quota to keep pre-S compatibility.
+                        lowQuota = true;
+                    } else {
+                        final String errorMessage = "Caller needs to hold "
+                                + Manifest.permission.SCHEDULE_EXACT_ALARM + " to set "
+                                + ((allowWhileIdle) ? "exact, allow-while-idle" : "alarm-clock")
+                                + " alarms.";
+                        throw new SecurityException(errorMessage);
+                    }
+                }
+                if (lowQuota) {
+                    flags &= ~FLAG_ALLOW_WHILE_IDLE;
+                    flags |= FLAG_ALLOW_WHILE_IDLE_COMPAT;
+                }
+            }
+
             // Repeating alarms must use PendingIntent, not direct listener
             if (interval != 0) {
                 if (directReceiver != null) {
@@ -1840,8 +2000,7 @@
 
             // No incoming callers can request either WAKE_FROM_IDLE or
             // ALLOW_WHILE_IDLE_UNRESTRICTED -- we will apply those later as appropriate.
-            flags &= ~(AlarmManager.FLAG_WAKE_FROM_IDLE
-                    | AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED);
+            flags &= ~(FLAG_WAKE_FROM_IDLE | FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED);
 
             // Only the system can use FLAG_IDLE_UNTIL -- this is used to tell the alarm
             // manager when to come out of idle mode, which is only for DeviceIdleController.
@@ -1857,22 +2016,32 @@
             // If this alarm is for an alarm clock, then it must be standalone and we will
             // use it to wake early from idle if needed.
             if (alarmClock != null) {
-                flags |= AlarmManager.FLAG_WAKE_FROM_IDLE | AlarmManager.FLAG_STANDALONE;
+                flags |= FLAG_WAKE_FROM_IDLE | AlarmManager.FLAG_STANDALONE;
 
             // If the caller is a core system component or on the user's whitelist, and not calling
             // to do work on behalf of someone else, then always set ALLOW_WHILE_IDLE_UNRESTRICTED.
             // This means we will allow these alarms to go off as normal even while idle, with no
             // timing restrictions.
-            } else if (workSource == null && (callingUid < Process.FIRST_APPLICATION_UID
+            } else if (workSource == null && (UserHandle.isCore(callingUid)
                     || UserHandle.isSameApp(callingUid, mSystemUiUid)
                     || ((mAppStateTracker != null)
                         && mAppStateTracker.isUidPowerSaveUserExempt(callingUid)))) {
-                flags |= AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
-                flags &= ~AlarmManager.FLAG_ALLOW_WHILE_IDLE;
+                flags |= FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
+                flags &= ~FLAG_ALLOW_WHILE_IDLE;
+                flags &= ~FLAG_ALLOW_WHILE_IDLE_COMPAT;
+                idleOptions = null;
             }
 
             setImpl(type, triggerAtTime, windowLength, interval, operation, directReceiver,
-                    listenerTag, flags, workSource, alarmClock, callingUid, callingPackage);
+                    listenerTag, flags, workSource, alarmClock, callingUid, callingPackage,
+                    idleOptions);
+        }
+
+        @Override
+        public boolean canScheduleExactAlarms() {
+            return PermissionChecker.checkCallingOrSelfPermissionForPreflight(getContext(),
+                    Manifest.permission.SCHEDULE_EXACT_ALARM)
+                    == PermissionChecker.PERMISSION_GRANTED;
         }
 
         @Override
@@ -2755,6 +2924,77 @@
         }
     }
 
+    /**
+     * Called when an app loses {@link Manifest.permission#SCHEDULE_EXACT_ALARM} to remove alarms
+     * that the app is no longer eligible to use.
+     * TODO (b/179541791): Revisit and write tests once UX is final.
+     */
+    void removeExactAlarmsOnPermissionRevokedLocked(int uid, String packageName) {
+        if (UserHandle.isCore(uid) || uid == mSystemUiUid) {
+            return;
+        }
+        if (isWhitelisted(uid)) {
+            return;
+        }
+        if (!CompatChanges.isChangeEnabled(
+                AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION,
+                packageName, UserHandle.getUserHandleForUid(uid))) {
+            return;
+        }
+
+        final Predicate<Alarm> whichAlarms =
+                a -> (a.uid == uid && a.packageName.equals(packageName)
+                        && ((a.flags & FLAG_ALLOW_WHILE_IDLE) != 0 || a.alarmClock != null));
+        final ArrayList<Alarm> removed = mAlarmStore.remove(whichAlarms);
+        final boolean didRemove = !removed.isEmpty();
+        if (didRemove) {
+            decrementAlarmCount(uid, removed.size());
+        }
+
+        for (int i = mPendingBackgroundAlarms.size() - 1; i >= 0; i--) {
+            final ArrayList<Alarm> alarmsForUid = mPendingBackgroundAlarms.valueAt(i);
+            for (int j = alarmsForUid.size() - 1; j >= 0; j--) {
+                final Alarm alarm = alarmsForUid.get(j);
+                if (whichAlarms.test(alarm)) {
+                    // Don't set didRemove, since this doesn't impact the scheduled alarms.
+                    alarmsForUid.remove(j);
+                    decrementAlarmCount(alarm.uid, 1);
+                }
+            }
+            if (alarmsForUid.size() == 0) {
+                mPendingBackgroundAlarms.removeAt(i);
+            }
+        }
+        for (int i = mPendingNonWakeupAlarms.size() - 1; i >= 0; i--) {
+            final Alarm a = mPendingNonWakeupAlarms.get(i);
+            if (whichAlarms.test(a)) {
+                // Don't set didRemove, since this doesn't impact the scheduled alarms.
+                mPendingNonWakeupAlarms.remove(i);
+                decrementAlarmCount(a.uid, 1);
+            }
+        }
+
+        if (didRemove) {
+            if (mNextWakeFromIdle != null && whichAlarms.test(mNextWakeFromIdle)) {
+                mNextWakeFromIdle = mAlarmStore.getNextWakeFromIdleAlarm();
+                if (mPendingIdleUntil != null) {
+                    final boolean idleUntilUpdated = mAlarmStore.updateAlarmDeliveries(alarm -> {
+                        if (alarm != mPendingIdleUntil) {
+                            return false;
+                        }
+                        return adjustIdleUntilTime(alarm);
+                    });
+                    if (idleUntilUpdated) {
+                        mAlarmStore.updateAlarmDeliveries(
+                                alarm -> adjustDeliveryTimeBasedOnDeviceIdle(alarm));
+                    }
+                }
+            }
+            rescheduleKernelAlarmsLocked();
+            updateNextAlarmClockLocked();
+        }
+    }
+
     void removeLocked(PendingIntent operation, IAlarmListener directReceiver) {
         if (operation == null && directReceiver == null) {
             if (localLOGV) {
@@ -3082,7 +3322,7 @@
         }
     }
 
-    private boolean isExemptFromBatterySaver(Alarm alarm) {
+    private static boolean isExemptFromBatterySaver(Alarm alarm) {
         if (alarm.alarmClock != null) {
             return true;
         }
@@ -3142,7 +3382,7 @@
 
             alarm.count = 1;
             triggerList.add(alarm);
-            if ((alarm.flags & AlarmManager.FLAG_WAKE_FROM_IDLE) != 0) {
+            if ((alarm.flags & FLAG_WAKE_FROM_IDLE) != 0) {
                 EventLogTags.writeDeviceIdleWakeFromIdle(mPendingIdleUntil != null ? 1 : 0,
                         alarm.statsTag);
             }
@@ -3180,7 +3420,7 @@
                 setImplLocked(alarm.type, alarm.origWhen + delta, nextElapsed,
                         nextMaxElapsed - nextElapsed, alarm.repeatInterval, alarm.operation, null,
                         null, alarm.flags, alarm.workSource, alarm.alarmClock, alarm.uid,
-                        alarm.packageName);
+                        alarm.packageName, null);
             }
 
             if (alarm.wakeup) {
@@ -3257,7 +3497,6 @@
         mLastAlarmDeliveryTime = nowELAPSED;
         for (int i = 0; i < triggerList.size(); i++) {
             Alarm alarm = triggerList.get(i);
-            final boolean allowWhileIdle = (alarm.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0;
             if (alarm.wakeup) {
                 Trace.traceBegin(Trace.TRACE_TAG_POWER,
                         "Dispatch wakeup alarm to " + alarm.packageName);
@@ -3273,7 +3512,7 @@
                     mActivityManagerInternal.noteAlarmStart(alarm.operation, alarm.workSource,
                             alarm.uid, alarm.statsTag);
                 }
-                mDeliveryTracker.deliverLocked(alarm, nowELAPSED, allowWhileIdle);
+                mDeliveryTracker.deliverLocked(alarm, nowELAPSED);
             } catch (RuntimeException e) {
                 Slog.w(TAG, "Failure sending alarm.", e);
             }
@@ -3282,9 +3521,10 @@
         }
     }
 
-    private boolean isExemptFromAppStandby(Alarm a) {
+    @VisibleForTesting
+    static boolean isExemptFromAppStandby(Alarm a) {
         return a.alarmClock != null || UserHandle.isCore(a.creatorUid)
-                || (a.flags & FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED) != 0;
+                || (a.flags & (FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED | FLAG_ALLOW_WHILE_IDLE)) != 0;
     }
 
     @VisibleForTesting
@@ -3368,6 +3608,11 @@
                     MATCH_SYSTEM_ONLY, USER_SYSTEM);
         }
 
+        IAppOpsService getAppOpsService() {
+            return IAppOpsService.Stub.asInterface(
+                    ServiceManager.getService(Context.APP_OPS_SERVICE));
+        }
+
         ClockReceiver getClockReceiver(AlarmManagerService service) {
             return service.new ClockReceiver();
         }
@@ -3566,6 +3811,7 @@
         public static final int APP_STANDBY_BUCKET_CHANGED = 5;
         public static final int CHARGING_STATUS_CHANGED = 6;
         public static final int REMOVE_FOR_CANCELED = 7;
+        public static final int REMOVE_EXACT_ALARMS = 8;
 
         AlarmHandler() {
             super(Looper.myLooper());
@@ -3645,6 +3891,14 @@
                     }
                     break;
 
+                case REMOVE_EXACT_ALARMS:
+                    final int uid = msg.arg1;
+                    final String packageName = (String) msg.obj;
+                    synchronized (mLock) {
+                        removeExactAlarmsOnPermissionRevokedLocked(uid, packageName);
+                    }
+                    break;
+
                 default:
                     // nope, just ignore it
                     break;
@@ -3720,7 +3974,7 @@
 
             setImpl(ELAPSED_REALTIME, mInjector.getElapsedRealtime() + tickEventDelay, 0,
                     0, null, mTimeTickTrigger, TIME_TICK_TAG, flags, workSource, null,
-                    Process.myUid(), "android");
+                    Process.myUid(), "android", null);
 
             // Finally, remember when we set the tick alarm
             synchronized (mLock) {
@@ -3740,7 +3994,7 @@
             final WorkSource workSource = null; // Let system take blame for date change events.
             setImpl(RTC, calendar.getTimeInMillis(), 0, 0, mDateChangeSender, null, null,
                     AlarmManager.FLAG_STANDALONE, workSource, null,
-                    Process.myUid(), "android");
+                    Process.myUid(), "android", null);
         }
     }
 
@@ -4112,7 +4366,7 @@
          * Deliver an alarm and set up the post-delivery handling appropriately
          */
         @GuardedBy("mLock")
-        public void deliverLocked(Alarm alarm, long nowELAPSED, boolean allowWhileIdle) {
+        public void deliverLocked(Alarm alarm, long nowELAPSED) {
             final long workSourceToken = ThreadLocalWorkSource.setUid(
                     getAlarmAttributionUid(alarm));
             try {
@@ -4122,10 +4376,8 @@
 
                     try {
                         alarm.operation.send(getContext(), 0,
-                                mBackgroundIntent.putExtra(
-                                        Intent.EXTRA_ALARM_COUNT, alarm.count),
-                                mDeliveryTracker, mHandler, null,
-                                allowWhileIdle ? mIdleOptions : null);
+                                mBackgroundIntent.putExtra(Intent.EXTRA_ALARM_COUNT, alarm.count),
+                                mDeliveryTracker, mHandler, null, alarm.mIdleOptions);
                     } catch (PendingIntent.CanceledException e) {
                         if (alarm.repeatInterval > 0) {
                             // This IntentSender is no longer valid, but this
@@ -4194,7 +4446,7 @@
             if (inflight.isBroadcast()) {
                 notifyBroadcastAlarmPendingLocked(alarm.uid);
             }
-            if (allowWhileIdle) {
+            if (isAllowedWhileIdleRestricted(alarm)) {
                 final boolean doze = (mPendingIdleUntil != null);
                 final boolean batterySaver = (mAppStateTracker != null
                         && mAppStateTracker.isForceAllAppsStandbyEnabled());
@@ -4204,8 +4456,7 @@
                     mAllowWhileIdleHistory.recordAlarmForPackage(alarm.sourcePackage,
                             UserHandle.getUserId(alarm.creatorUid), nowELAPSED);
                     mAlarmStore.updateAlarmDeliveries(a -> {
-                        if (a.creatorUid != alarm.creatorUid
-                                || (a.flags & FLAG_ALLOW_WHILE_IDLE) == 0) {
+                        if (a.creatorUid != alarm.creatorUid || !isAllowedWhileIdleRestricted(a)) {
                             return false;
                         }
                         return (doze && adjustDeliveryTimeBasedOnDeviceIdle(a))
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 3cefe65..164781a 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -741,17 +741,26 @@
 
         pw.increaseIndent();
         try {
-            pw.print("Configuration:");
+            pw.println("Configuration:");
             pw.increaseIndent();
             pw.print(KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS, mScreenOffAdjustmentDelayMs).println();
+            pw.println();
             CONFIG_LIMITS_SCREEN_ON.normal.dump(pw);
+            pw.println();
             CONFIG_LIMITS_SCREEN_ON.moderate.dump(pw);
+            pw.println();
             CONFIG_LIMITS_SCREEN_ON.low.dump(pw);
+            pw.println();
             CONFIG_LIMITS_SCREEN_ON.critical.dump(pw);
+            pw.println();
             CONFIG_LIMITS_SCREEN_OFF.normal.dump(pw);
+            pw.println();
             CONFIG_LIMITS_SCREEN_OFF.moderate.dump(pw);
+            pw.println();
             CONFIG_LIMITS_SCREEN_OFF.low.dump(pw);
+            pw.println();
             CONFIG_LIMITS_SCREEN_OFF.critical.dump(pw);
+            pw.println();
             pw.decreaseIndent();
 
             pw.print("Screen state: current ");
@@ -770,18 +779,17 @@
 
             pw.println();
 
-            pw.println("Current max jobs:");
-            pw.println("  ");
+            pw.print("Current work counts: ");
             pw.println(mWorkCountTracker);
 
             pw.println();
 
             pw.print("mLastMemoryTrimLevel: ");
-            pw.print(mLastMemoryTrimLevel);
+            pw.println(mLastMemoryTrimLevel);
             pw.println();
 
             pw.print("User Grace Period: ");
-            pw.print(mGracePeriodObserver.mGracePeriodExpiration);
+            pw.println(mGracePeriodObserver.mGracePeriodExpiration);
             pw.println();
 
             mStatLogger.dump(pw);
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 96f3bcc..fdbc086 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -3040,7 +3040,6 @@
             pw.println();
 
             for (int i = mJobRestrictions.size() - 1; i >= 0; i--) {
-                pw.print("    ");
                 mJobRestrictions.get(i).dumpConstants(pw);
                 pw.println();
             }
@@ -3067,7 +3066,6 @@
 
                     job.dump(pw, "    ", true, nowElapsed);
 
-
                     pw.print("    Restricted due to:");
                     final boolean isRestricted = checkIfRestricted(job) != null;
                     if (isRestricted) {
@@ -3161,39 +3159,28 @@
             }
             pw.println();
             pw.println("Active jobs:");
+            pw.increaseIndent();
             for (int i=0; i<mActiveServices.size(); i++) {
                 JobServiceContext jsc = mActiveServices.get(i);
-                pw.print("  Slot #"); pw.print(i); pw.print(": ");
-                final JobStatus job = jsc.getRunningJobLocked();
-                if (job == null) {
-                    if (jsc.mStoppedReason != null) {
-                        pw.print("inactive since ");
-                        TimeUtils.formatDuration(jsc.mStoppedTime, nowElapsed, pw);
-                        pw.print(", stopped because: ");
-                        pw.println(jsc.mStoppedReason);
-                    } else {
-                        pw.println("inactive");
-                    }
-                    continue;
-                } else {
-                    pw.println(job.toShortString());
-                    pw.print("    Running for: ");
-                    TimeUtils.formatDuration(nowElapsed - jsc.getExecutionStartTimeElapsed(), pw);
-                    pw.print(", timeout at: ");
-                    TimeUtils.formatDuration(jsc.getTimeoutElapsed() - nowElapsed, pw);
-                    pw.println();
-                    job.dump(pw, "    ", false, nowElapsed);
-                    int priority = evaluateJobPriorityLocked(job);
-                    pw.print("    Evaluated priority: ");
-                    pw.println(JobInfo.getPriorityString(priority));
+                pw.print("Slot #"); pw.print(i); pw.print(": ");
+                jsc.dumpLocked(pw, nowElapsed);
 
-                    pw.print("    Active at ");
+                final JobStatus job = jsc.getRunningJobLocked();
+                if (job != null) {
+                    pw.increaseIndent();
+                    job.dump(pw, "  ", false, nowElapsed);
+                    pw.print("Evaluated priority: ");
+                    pw.println(JobInfo.getPriorityString(job.lastEvaluatedPriority));
+
+                    pw.print("Active at ");
                     TimeUtils.formatDuration(job.madeActive - nowUptime, pw);
                     pw.print(", pending for ");
                     TimeUtils.formatDuration(job.madeActive - job.madePending, pw);
                     pw.println();
+                    pw.decreaseIndent();
                 }
             }
+            pw.decreaseIndent();
             if (filterUid == -1) {
                 pw.println();
                 pw.print("mReadyToRock="); pw.println(mReadyToRock);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index da6f9fe..0aca246 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -43,6 +43,7 @@
 import android.os.UserHandle;
 import android.os.WorkSource;
 import android.util.EventLog;
+import android.util.IndentingPrintWriter;
 import android.util.Slog;
 import android.util.TimeUtils;
 
@@ -901,8 +902,30 @@
         mTimeoutElapsed = sElapsedRealtimeClock.millis() + timeoutMillis;
     }
 
-
     private void removeOpTimeOutLocked() {
         mCallbackHandler.removeMessages(MSG_TIMEOUT);
     }
+
+    void dumpLocked(IndentingPrintWriter pw, final long nowElapsed) {
+        if (mRunningJob == null) {
+            if (mStoppedReason != null) {
+                pw.print("inactive since ");
+                TimeUtils.formatDuration(mStoppedTime, nowElapsed, pw);
+                pw.print(", stopped because: ");
+                pw.println(mStoppedReason);
+            } else {
+                pw.println("inactive");
+            }
+        } else {
+            pw.println(mRunningJob.toShortString());
+
+            pw.increaseIndent();
+            pw.print("Running for: ");
+            TimeUtils.formatDuration(nowElapsed - mExecutionStartTimeElapsed, pw);
+            pw.print(", timeout at: ");
+            TimeUtils.formatDuration(mTimeoutElapsed - nowElapsed, pw);
+            pw.println();
+            pw.decreaseIndent();
+        }
+    }
 }
diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp
index 3bad889..5d64579 100644
--- a/cmds/idmap2/Android.bp
+++ b/cmds/idmap2/Android.bp
@@ -26,10 +26,24 @@
     tidy_checks_as_errors: [
         "modernize-*",
         "-modernize-avoid-c-arrays",
+        "-modernize-pass-by-value",
+        "-modernize-replace-disallow-copy-and-assign-macro",
+        "-modernize-use-equals-default",
+        "-modernize-use-nodiscard",
+        "-modernize-use-override",
         "-modernize-use-trailing-return-type",
+        "-modernize-use-using",
         "android-*",
         "misc-*",
+        "-misc-non-private-member-variables-in-classes",
         "readability-*",
+        "-readability-braces-around-statements",
+        "-readability-const-return-type",
+        "-readability-convert-member-functions-to-static",
+        "-readability-else-after-return",
+        "-readability-named-parameter",
+        "-readability-redundant-access-specifiers",
+        "-readability-uppercase-literal-suffix",
     ],
     tidy_flags: [
         "-system-headers",
diff --git a/core/api/current.txt b/core/api/current.txt
index 0e64a9d..a21b693 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -145,6 +145,7 @@
     field public static final String REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE = "android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE";
     field public static final String REQUEST_PASSWORD_COMPLEXITY = "android.permission.REQUEST_PASSWORD_COMPLEXITY";
     field @Deprecated public static final String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES";
+    field public static final String SCHEDULE_EXACT_ALARM = "android.permission.SCHEDULE_EXACT_ALARM";
     field public static final String SEND_RESPOND_VIA_MESSAGE = "android.permission.SEND_RESPOND_VIA_MESSAGE";
     field public static final String SEND_SMS = "android.permission.SEND_SMS";
     field public static final String SET_ALARM = "com.android.alarm.permission.SET_ALARM";
@@ -1713,30 +1714,42 @@
     field @Deprecated public static final int secondary_text_dark_nodisable = 17170438; // 0x1060006
     field @Deprecated public static final int secondary_text_light = 17170439; // 0x1060007
     field @Deprecated public static final int secondary_text_light_nodisable = 17170440; // 0x1060008
-    field public static final int system_accent_0 = 17170473; // 0x1060029
-    field public static final int system_accent_100 = 17170475; // 0x106002b
-    field public static final int system_accent_1000 = 17170484; // 0x1060034
-    field public static final int system_accent_200 = 17170476; // 0x106002c
-    field public static final int system_accent_300 = 17170477; // 0x106002d
-    field public static final int system_accent_400 = 17170478; // 0x106002e
-    field public static final int system_accent_50 = 17170474; // 0x106002a
-    field public static final int system_accent_500 = 17170479; // 0x106002f
-    field public static final int system_accent_600 = 17170480; // 0x1060030
-    field public static final int system_accent_700 = 17170481; // 0x1060031
-    field public static final int system_accent_800 = 17170482; // 0x1060032
-    field public static final int system_accent_900 = 17170483; // 0x1060033
-    field public static final int system_main_0 = 17170461; // 0x106001d
-    field public static final int system_main_100 = 17170463; // 0x106001f
-    field public static final int system_main_1000 = 17170472; // 0x1060028
-    field public static final int system_main_200 = 17170464; // 0x1060020
-    field public static final int system_main_300 = 17170465; // 0x1060021
-    field public static final int system_main_400 = 17170466; // 0x1060022
-    field public static final int system_main_50 = 17170462; // 0x106001e
-    field public static final int system_main_500 = 17170467; // 0x1060023
-    field public static final int system_main_600 = 17170468; // 0x1060024
-    field public static final int system_main_700 = 17170469; // 0x1060025
-    field public static final int system_main_800 = 17170470; // 0x1060026
-    field public static final int system_main_900 = 17170471; // 0x1060027
+    field public static final int system_neutral_0 = 17170485; // 0x1060035
+    field public static final int system_neutral_100 = 17170487; // 0x1060037
+    field public static final int system_neutral_1000 = 17170496; // 0x1060040
+    field public static final int system_neutral_200 = 17170488; // 0x1060038
+    field public static final int system_neutral_300 = 17170489; // 0x1060039
+    field public static final int system_neutral_400 = 17170490; // 0x106003a
+    field public static final int system_neutral_50 = 17170486; // 0x1060036
+    field public static final int system_neutral_500 = 17170491; // 0x106003b
+    field public static final int system_neutral_600 = 17170492; // 0x106003c
+    field public static final int system_neutral_700 = 17170493; // 0x106003d
+    field public static final int system_neutral_800 = 17170494; // 0x106003e
+    field public static final int system_neutral_900 = 17170495; // 0x106003f
+    field public static final int system_primary_0 = 17170461; // 0x106001d
+    field public static final int system_primary_100 = 17170463; // 0x106001f
+    field public static final int system_primary_1000 = 17170472; // 0x1060028
+    field public static final int system_primary_200 = 17170464; // 0x1060020
+    field public static final int system_primary_300 = 17170465; // 0x1060021
+    field public static final int system_primary_400 = 17170466; // 0x1060022
+    field public static final int system_primary_50 = 17170462; // 0x106001e
+    field public static final int system_primary_500 = 17170467; // 0x1060023
+    field public static final int system_primary_600 = 17170468; // 0x1060024
+    field public static final int system_primary_700 = 17170469; // 0x1060025
+    field public static final int system_primary_800 = 17170470; // 0x1060026
+    field public static final int system_primary_900 = 17170471; // 0x1060027
+    field public static final int system_secondary_0 = 17170473; // 0x1060029
+    field public static final int system_secondary_100 = 17170475; // 0x106002b
+    field public static final int system_secondary_1000 = 17170484; // 0x1060034
+    field public static final int system_secondary_200 = 17170476; // 0x106002c
+    field public static final int system_secondary_300 = 17170477; // 0x106002d
+    field public static final int system_secondary_400 = 17170478; // 0x106002e
+    field public static final int system_secondary_50 = 17170474; // 0x106002a
+    field public static final int system_secondary_500 = 17170479; // 0x106002f
+    field public static final int system_secondary_600 = 17170480; // 0x1060030
+    field public static final int system_secondary_700 = 17170481; // 0x1060031
+    field public static final int system_secondary_800 = 17170482; // 0x1060032
+    field public static final int system_secondary_900 = 17170483; // 0x1060033
     field public static final int tab_indicator_text = 17170441; // 0x1060009
     field @Deprecated public static final int tertiary_text_dark = 17170448; // 0x1060010
     field @Deprecated public static final int tertiary_text_light = 17170449; // 0x1060011
@@ -4316,16 +4329,17 @@
   }
 
   public class AlarmManager {
+    method public boolean canScheduleExactAlarms();
     method public void cancel(android.app.PendingIntent);
     method public void cancel(android.app.AlarmManager.OnAlarmListener);
     method public android.app.AlarmManager.AlarmClockInfo getNextAlarmClock();
     method public void set(int, long, android.app.PendingIntent);
     method public void set(int, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
-    method public void setAlarmClock(android.app.AlarmManager.AlarmClockInfo, android.app.PendingIntent);
+    method @RequiresPermission(android.Manifest.permission.SCHEDULE_EXACT_ALARM) public void setAlarmClock(android.app.AlarmManager.AlarmClockInfo, android.app.PendingIntent);
     method public void setAndAllowWhileIdle(int, long, android.app.PendingIntent);
     method public void setExact(int, long, android.app.PendingIntent);
     method public void setExact(int, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
-    method public void setExactAndAllowWhileIdle(int, long, android.app.PendingIntent);
+    method @RequiresPermission(value=android.Manifest.permission.SCHEDULE_EXACT_ALARM, conditional=true) public void setExactAndAllowWhileIdle(int, long, android.app.PendingIntent);
     method public void setInexactRepeating(int, long, long, android.app.PendingIntent);
     method public void setRepeating(int, long, long, android.app.PendingIntent);
     method @RequiresPermission(android.Manifest.permission.SET_TIME) public void setTime(long);
@@ -7092,6 +7106,7 @@
     method public boolean isManagedProfile(@NonNull android.content.ComponentName);
     method public boolean isMasterVolumeMuted(@NonNull android.content.ComponentName);
     method public boolean isNetworkLoggingEnabled(@Nullable android.content.ComponentName);
+    method public boolean isNetworkSlicingEnabled();
     method public boolean isOrganizationOwnedDeviceWithManagedProfile();
     method public boolean isOverrideApnEnabled(@NonNull android.content.ComponentName);
     method public boolean isPackageSuspended(@NonNull android.content.ComponentName, String) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -7104,6 +7119,7 @@
     method public boolean isUniqueDeviceAttestationSupported();
     method public boolean isUsbDataSignalingEnabled();
     method public boolean isUsingUnifiedPassword(@NonNull android.content.ComponentName);
+    method @NonNull public java.util.List<android.os.UserHandle> listForegroundAffiliatedUsers();
     method public void lockNow();
     method public void lockNow(int);
     method public int logoutUser(@NonNull android.content.ComponentName);
@@ -7163,6 +7179,7 @@
     method public void setMaximumTimeToLock(@NonNull android.content.ComponentName, long);
     method @NonNull public java.util.List<java.lang.String> setMeteredDataDisabledPackages(@NonNull android.content.ComponentName, @NonNull java.util.List<java.lang.String>);
     method public void setNetworkLoggingEnabled(@Nullable android.content.ComponentName, boolean);
+    method public void setNetworkSlicingEnabled(boolean);
     method @Deprecated public void setOrganizationColor(@NonNull android.content.ComponentName, int);
     method public void setOrganizationId(@NonNull String);
     method public void setOrganizationName(@NonNull android.content.ComponentName, @Nullable CharSequence);
@@ -12206,6 +12223,7 @@
     method public void setAutoRevokePermissionsMode(boolean);
     method public void setInstallLocation(int);
     method public void setInstallReason(int);
+    method public void setInstallScenario(int);
     method public void setMultiPackage();
     method public void setOriginatingUid(int);
     method public void setOriginatingUri(@Nullable android.net.Uri);
@@ -12428,6 +12446,7 @@
     field public static final String FEATURE_INPUT_METHODS = "android.software.input_methods";
     field public static final String FEATURE_IPSEC_TUNNELS = "android.software.ipsec_tunnels";
     field public static final String FEATURE_IRIS = "android.hardware.biometrics.iris";
+    field public static final String FEATURE_KEYSTORE_APP_ATTEST_KEY = "android.hardware.keystore.app_attest_key";
     field public static final String FEATURE_KEYSTORE_LIMITED_USE_KEY = "android.hardware.keystore.limited_use_key";
     field public static final String FEATURE_KEYSTORE_SINGLE_USE_KEY = "android.hardware.keystore.single_use_key";
     field public static final String FEATURE_LEANBACK = "android.software.leanback";
@@ -12534,6 +12553,10 @@
     field public static final int INSTALL_REASON_POLICY = 1; // 0x1
     field public static final int INSTALL_REASON_UNKNOWN = 0; // 0x0
     field public static final int INSTALL_REASON_USER = 4; // 0x4
+    field public static final int INSTALL_SCENARIO_BULK = 2; // 0x2
+    field public static final int INSTALL_SCENARIO_BULK_SECONDARY = 3; // 0x3
+    field public static final int INSTALL_SCENARIO_DEFAULT = 0; // 0x0
+    field public static final int INSTALL_SCENARIO_FAST = 1; // 0x1
     field public static final int MATCH_ALL = 131072; // 0x20000
     field public static final int MATCH_APEX = 1073741824; // 0x40000000
     field public static final int MATCH_DEFAULT_ONLY = 65536; // 0x10000
@@ -17686,6 +17709,7 @@
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Float> SCALER_AVAILABLE_MAX_DIGITAL_ZOOM;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> SCALER_AVAILABLE_ROTATE_AND_CROP_MODES;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SCALER_CROPPING_TYPE;
+    field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Size> SCALER_DEFAULT_SECURE_IMAGE_SIZE;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_CONCURRENT_STREAM_COMBINATIONS;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_STREAM_COMBINATIONS;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.StreamConfigurationMap> SCALER_STREAM_CONFIGURATION_MAP;
@@ -20993,7 +21017,7 @@
     method @NonNull public String getDiagnosticInfo();
   }
 
-  public final class MediaCodec {
+  public final class MediaCodec implements android.media.metrics.PlaybackComponent {
     method public void configure(@Nullable android.media.MediaFormat, @Nullable android.view.Surface, @Nullable android.media.MediaCrypto, int);
     method public void configure(@Nullable android.media.MediaFormat, @Nullable android.view.Surface, int, @Nullable android.media.MediaDescrambler);
     method @NonNull public static android.media.MediaCodec createByCodecName(@NonNull String) throws java.io.IOException;
@@ -21019,6 +21043,7 @@
     method @NonNull public android.media.MediaFormat getOutputFormat(int);
     method @NonNull public android.media.MediaCodec.OutputFrame getOutputFrame(int);
     method @Nullable public android.media.Image getOutputImage(int);
+    method public String getPlaybackId();
     method @NonNull public android.media.MediaCodec.QueueRequest getQueueRequest(int);
     method @Nullable public static android.media.Image mapHardwareBuffer(@NonNull android.hardware.HardwareBuffer);
     method public void queueInputBuffer(int, int, int, long, int) throws android.media.MediaCodec.CryptoException;
@@ -21034,6 +21059,7 @@
     method public void setOnFrameRenderedListener(@Nullable android.media.MediaCodec.OnFrameRenderedListener, @Nullable android.os.Handler);
     method public void setOutputSurface(@NonNull android.view.Surface);
     method public void setParameters(@Nullable android.os.Bundle);
+    method public void setPlaybackId(@NonNull String);
     method public void setVideoScalingMode(int);
     method public void signalEndOfInputStream();
     method public void start();
@@ -21634,6 +21660,7 @@
     method @android.media.MediaDrm.HdcpLevel public int getConnectedHdcpLevel();
     method public android.media.MediaDrm.CryptoSession getCryptoSession(@NonNull byte[], @NonNull String, @NonNull String);
     method @NonNull public android.media.MediaDrm.KeyRequest getKeyRequest(@NonNull byte[], @Nullable byte[], @Nullable String, int, @Nullable java.util.HashMap<java.lang.String,java.lang.String>) throws android.media.NotProvisionedException;
+    method @NonNull public java.util.List<android.media.MediaDrm.LogMessage> getLogMessages();
     method @android.media.MediaDrm.HdcpLevel public int getMaxHdcpLevel();
     method public static int getMaxSecurityLevel();
     method public int getMaxSessionCount();
@@ -21742,6 +21769,12 @@
     field public static final int STATUS_USABLE_IN_FUTURE = 5; // 0x5
   }
 
+  public static class MediaDrm.LogMessage {
+    field @NonNull public final String message;
+    field public final int priority;
+    field public final long timestampMillis;
+  }
+
   public static final class MediaDrm.MediaDrmStateException extends java.lang.IllegalStateException {
     method @NonNull public String getDiagnosticInfo();
   }
@@ -24100,6 +24133,34 @@
     field public static final long INVALID_TIMESTAMP = -1L; // 0xffffffffffffffffL
   }
 
+  public final class NetworkEvent extends android.media.metrics.Event implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getNetworkType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.metrics.NetworkEvent> CREATOR;
+    field public static final int NETWORK_TYPE_2G = 4; // 0x4
+    field public static final int NETWORK_TYPE_3G = 5; // 0x5
+    field public static final int NETWORK_TYPE_4G = 6; // 0x6
+    field public static final int NETWORK_TYPE_5G_NSA = 7; // 0x7
+    field public static final int NETWORK_TYPE_5G_SA = 8; // 0x8
+    field public static final int NETWORK_TYPE_ETHERNET = 3; // 0x3
+    field public static final int NETWORK_TYPE_NONE = 0; // 0x0
+    field public static final int NETWORK_TYPE_OTHER = 1; // 0x1
+    field public static final int NETWORK_TYPE_WIFI = 2; // 0x2
+  }
+
+  public static final class NetworkEvent.Builder {
+    ctor public NetworkEvent.Builder();
+    method @NonNull public android.media.metrics.NetworkEvent build();
+    method @NonNull public android.media.metrics.NetworkEvent.Builder setNetworkType(int);
+    method @NonNull public android.media.metrics.NetworkEvent.Builder setTimeSinceCreatedMillis(@IntRange(from=0xffffffff) long);
+  }
+
+  public interface PlaybackComponent {
+    method @NonNull public String getPlaybackId();
+    method public void setPlaybackId(@NonNull String);
+  }
+
   public final class PlaybackErrorEvent extends android.media.metrics.Event implements android.os.Parcelable {
     method public int describeContents();
     method public int getErrorCode();
@@ -24120,11 +24181,78 @@
     method @NonNull public android.media.metrics.PlaybackErrorEvent.Builder setTimeSinceCreatedMillis(@IntRange(from=0xffffffff) long);
   }
 
+  public final class PlaybackMetrics implements android.os.Parcelable {
+    method public int describeContents();
+    method @IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) public int getAudioUnderrunCount();
+    method public int getContentType();
+    method public int getDrmType();
+    method @NonNull public long[] getExperimentIds();
+    method @IntRange(from=0xffffffff) public long getLocalBytesRead();
+    method @IntRange(from=0xffffffff) public long getMediaDurationMillis();
+    method @IntRange(from=0xffffffff) public long getNetworkBytesRead();
+    method @IntRange(from=0xffffffff) public long getNetworkTransferDurationMillis();
+    method public int getPlaybackType();
+    method @Nullable public String getPlayerName();
+    method @Nullable public String getPlayerVersion();
+    method public int getStreamSource();
+    method public int getStreamType();
+    method @IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) public int getVideoFramesDropped();
+    method @IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) public int getVideoFramesPlayed();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field public static final int CONTENT_TYPE_AD = 1; // 0x1
+    field public static final int CONTENT_TYPE_MAIN = 0; // 0x0
+    field public static final int CONTENT_TYPE_OTHER = 2; // 0x2
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.metrics.PlaybackMetrics> CREATOR;
+    field public static final int DRM_TYPE_CLEARKEY = 6; // 0x6
+    field public static final int DRM_TYPE_NONE = 0; // 0x0
+    field public static final int DRM_TYPE_OTHER = 1; // 0x1
+    field public static final int DRM_TYPE_PLAY_READY = 2; // 0x2
+    field public static final int DRM_TYPE_WIDEVINE_L1 = 3; // 0x3
+    field public static final int DRM_TYPE_WIDEVINE_L3 = 4; // 0x4
+    field public static final int DRM_TYPE_WV_L3_FALLBACK = 5; // 0x5
+    field public static final int PLAYBACK_TYPE_LIVE = 1; // 0x1
+    field public static final int PLAYBACK_TYPE_OTHER = 2; // 0x2
+    field public static final int PLAYBACK_TYPE_VOD = 0; // 0x0
+    field public static final int STREAM_SOURCE_DEVICE = 2; // 0x2
+    field public static final int STREAM_SOURCE_MIXED = 3; // 0x3
+    field public static final int STREAM_SOURCE_NETWORK = 1; // 0x1
+    field public static final int STREAM_SOURCE_UNKNOWN = 0; // 0x0
+    field public static final int STREAM_TYPE_DASH = 3; // 0x3
+    field public static final int STREAM_TYPE_HLS = 4; // 0x4
+    field public static final int STREAM_TYPE_OTHER = 1; // 0x1
+    field public static final int STREAM_TYPE_PROGRESSIVE = 2; // 0x2
+    field public static final int STREAM_TYPE_SS = 5; // 0x5
+    field public static final int STREAM_TYPE_UNKNOWN = 0; // 0x0
+  }
+
+  public static final class PlaybackMetrics.Builder {
+    ctor public PlaybackMetrics.Builder();
+    method @NonNull public android.media.metrics.PlaybackMetrics.Builder addExperimentId(long);
+    method @NonNull public android.media.metrics.PlaybackMetrics build();
+    method @NonNull public android.media.metrics.PlaybackMetrics.Builder setAudioUnderrunCount(@IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) int);
+    method @NonNull public android.media.metrics.PlaybackMetrics.Builder setContentType(int);
+    method @NonNull public android.media.metrics.PlaybackMetrics.Builder setDrmType(int);
+    method @NonNull public android.media.metrics.PlaybackMetrics.Builder setLocalBytesRead(@IntRange(from=0xffffffff) long);
+    method @NonNull public android.media.metrics.PlaybackMetrics.Builder setMediaDurationMillis(@IntRange(from=0xffffffff) long);
+    method @NonNull public android.media.metrics.PlaybackMetrics.Builder setNetworkBytesRead(@IntRange(from=0xffffffff) long);
+    method @NonNull public android.media.metrics.PlaybackMetrics.Builder setNetworkTransferDurationMillis(@IntRange(from=0xffffffff) long);
+    method @NonNull public android.media.metrics.PlaybackMetrics.Builder setPlaybackType(int);
+    method @NonNull public android.media.metrics.PlaybackMetrics.Builder setPlayerName(@NonNull String);
+    method @NonNull public android.media.metrics.PlaybackMetrics.Builder setPlayerVersion(@NonNull String);
+    method @NonNull public android.media.metrics.PlaybackMetrics.Builder setStreamSource(int);
+    method @NonNull public android.media.metrics.PlaybackMetrics.Builder setStreamType(int);
+    method @NonNull public android.media.metrics.PlaybackMetrics.Builder setVideoFramesDropped(@IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) int);
+    method @NonNull public android.media.metrics.PlaybackMetrics.Builder setVideoFramesPlayed(@IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) int);
+  }
+
   public final class PlaybackSession implements java.lang.AutoCloseable {
     method public void close();
     method @NonNull public String getId();
+    method public void reportNetworkEvent(@NonNull android.media.metrics.NetworkEvent);
     method public void reportPlaybackErrorEvent(@NonNull android.media.metrics.PlaybackErrorEvent);
+    method public void reportPlaybackMetrics(@NonNull android.media.metrics.PlaybackMetrics);
     method public void reportPlaybackStateEvent(@NonNull android.media.metrics.PlaybackStateEvent);
+    method public void reportTrackChangeEvent(@NonNull android.media.metrics.TrackChangeEvent);
   }
 
   public final class PlaybackStateEvent extends android.media.metrics.Event implements android.os.Parcelable {
@@ -24156,6 +24284,54 @@
     method @NonNull public android.media.metrics.PlaybackStateEvent.Builder setTimeSinceCreatedMillis(@IntRange(from=0xffffffff) long);
   }
 
+  public final class TrackChangeEvent extends android.media.metrics.Event implements android.os.Parcelable {
+    ctor public TrackChangeEvent(int, int, @Nullable String, @Nullable String, @Nullable String, int, long, int, @Nullable String, @Nullable String, int, int, int, int);
+    method public int describeContents();
+    method @IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) public int getBitrate();
+    method @IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) public int getChannelCount();
+    method @Nullable public String getCodecName();
+    method @Nullable public String getContainerMimeType();
+    method @IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) public int getHeight();
+    method @Nullable public String getLanguage();
+    method @Nullable public String getLanguageRegion();
+    method @Nullable public String getSampleMimeType();
+    method @IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) public int getSampleRate();
+    method public int getTrackChangeReason();
+    method public int getTrackState();
+    method public int getTrackType();
+    method @IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) public int getWidth();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.metrics.TrackChangeEvent> CREATOR;
+    field public static final int TRACK_CHANGE_REASON_ADAPTIVE = 4; // 0x4
+    field public static final int TRACK_CHANGE_REASON_INITIAL = 2; // 0x2
+    field public static final int TRACK_CHANGE_REASON_MANUAL = 3; // 0x3
+    field public static final int TRACK_CHANGE_REASON_OTHER = 1; // 0x1
+    field public static final int TRACK_CHANGE_REASON_UNKNOWN = 0; // 0x0
+    field public static final int TRACK_STATE_OFF = 0; // 0x0
+    field public static final int TRACK_STATE_ON = 1; // 0x1
+    field public static final int TRACK_TYPE_AUDIO = 0; // 0x0
+    field public static final int TRACK_TYPE_TEXT = 2; // 0x2
+    field public static final int TRACK_TYPE_VIDEO = 1; // 0x1
+  }
+
+  public static final class TrackChangeEvent.Builder {
+    ctor public TrackChangeEvent.Builder(int);
+    method @NonNull public android.media.metrics.TrackChangeEvent build();
+    method @NonNull public android.media.metrics.TrackChangeEvent.Builder setBitrate(@IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) int);
+    method @NonNull public android.media.metrics.TrackChangeEvent.Builder setChannelCount(@IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) int);
+    method @NonNull public android.media.metrics.TrackChangeEvent.Builder setCodecName(@NonNull String);
+    method @NonNull public android.media.metrics.TrackChangeEvent.Builder setContainerMimeType(@NonNull String);
+    method @NonNull public android.media.metrics.TrackChangeEvent.Builder setHeight(@IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) int);
+    method @NonNull public android.media.metrics.TrackChangeEvent.Builder setLanguage(@NonNull String);
+    method @NonNull public android.media.metrics.TrackChangeEvent.Builder setLanguageRegion(@NonNull String);
+    method @NonNull public android.media.metrics.TrackChangeEvent.Builder setSampleMimeType(@NonNull String);
+    method @NonNull public android.media.metrics.TrackChangeEvent.Builder setSampleRate(@IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) int);
+    method @NonNull public android.media.metrics.TrackChangeEvent.Builder setTimeSinceCreatedMillis(@IntRange(from=0xffffffff) long);
+    method @NonNull public android.media.metrics.TrackChangeEvent.Builder setTrackChangeReason(int);
+    method @NonNull public android.media.metrics.TrackChangeEvent.Builder setTrackState(int);
+    method @NonNull public android.media.metrics.TrackChangeEvent.Builder setWidth(@IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) int);
+  }
+
 }
 
 package android.media.midi {
@@ -26794,6 +26970,46 @@
 
 }
 
+package android.net.vcn {
+
+  public final class VcnConfig implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.Set<android.net.vcn.VcnGatewayConnectionConfig> getGatewayConnectionConfigs();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.vcn.VcnConfig> CREATOR;
+  }
+
+  public static final class VcnConfig.Builder {
+    ctor public VcnConfig.Builder(@NonNull android.content.Context);
+    method @NonNull public android.net.vcn.VcnConfig.Builder addGatewayConnectionConfig(@NonNull android.net.vcn.VcnGatewayConnectionConfig);
+    method @NonNull public android.net.vcn.VcnConfig build();
+  }
+
+  public final class VcnGatewayConnectionConfig {
+    method @NonNull public int[] getExposedCapabilities();
+    method @IntRange(from=android.net.vcn.VcnGatewayConnectionConfig.MIN_MTU_V6) public int getMaxMtu();
+    method @NonNull public int[] getRequiredUnderlyingCapabilities();
+    method @NonNull public long[] getRetryInterval();
+  }
+
+  public static final class VcnGatewayConnectionConfig.Builder {
+    ctor public VcnGatewayConnectionConfig.Builder();
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder addExposedCapability(int);
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder addRequiredUnderlyingCapability(int);
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig build();
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeExposedCapability(int);
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeRequiredUnderlyingCapability(int);
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setMaxMtu(@IntRange(from=android.net.vcn.VcnGatewayConnectionConfig.MIN_MTU_V6) int);
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setRetryInterval(@NonNull long[]);
+  }
+
+  public class VcnManager {
+    method @RequiresPermission("carrier privileges") public void clearVcnConfig(@NonNull android.os.ParcelUuid) throws java.io.IOException;
+    method @RequiresPermission("carrier privileges") public void setVcnConfig(@NonNull android.os.ParcelUuid, @NonNull android.net.vcn.VcnConfig) throws java.io.IOException;
+  }
+
+}
+
 package android.nfc {
 
   public class FormatException extends java.lang.Exception {
@@ -31586,6 +31802,7 @@
     method public boolean isQuietModeEnabled(android.os.UserHandle);
     method public boolean isSystemUser();
     method public boolean isUserAGoat();
+    method public boolean isUserForeground();
     method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public boolean isUserRunning(android.os.UserHandle);
     method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public boolean isUserRunningOrStopping(android.os.UserHandle);
     method public boolean isUserUnlocked();
@@ -37037,6 +37254,7 @@
 
   public final class KeyGenParameterSpec implements java.security.spec.AlgorithmParameterSpec {
     method @Nullable public java.security.spec.AlgorithmParameterSpec getAlgorithmParameterSpec();
+    method @Nullable public String getAttestKeyAlias();
     method public byte[] getAttestationChallenge();
     method @NonNull public String[] getBlockModes();
     method @NonNull public java.util.Date getCertificateNotAfter();
@@ -37071,6 +37289,7 @@
     ctor public KeyGenParameterSpec.Builder(@NonNull String, int);
     method @NonNull public android.security.keystore.KeyGenParameterSpec build();
     method public android.security.keystore.KeyGenParameterSpec.Builder setAlgorithmParameterSpec(@NonNull java.security.spec.AlgorithmParameterSpec);
+    method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setAttestKeyAlias(@Nullable String);
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setAttestationChallenge(byte[]);
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setBlockModes(java.lang.String...);
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setCertificateNotAfter(@NonNull java.util.Date);
@@ -37168,6 +37387,7 @@
     field public static final int ORIGIN_SECURELY_IMPORTED = 8; // 0x8
     field public static final int ORIGIN_UNKNOWN = 4; // 0x4
     field public static final int PURPOSE_AGREE_KEY = 64; // 0x40
+    field public static final int PURPOSE_ATTEST_KEY = 128; // 0x80
     field public static final int PURPOSE_DECRYPT = 2; // 0x2
     field public static final int PURPOSE_ENCRYPT = 1; // 0x1
     field public static final int PURPOSE_SIGN = 4; // 0x4
@@ -38806,7 +39026,9 @@
     field public static final int ERROR_NO_MATCH = 7; // 0x7
     field public static final int ERROR_RECOGNIZER_BUSY = 8; // 0x8
     field public static final int ERROR_SERVER = 4; // 0x4
+    field public static final int ERROR_SERVER_DISCONNECTED = 11; // 0xb
     field public static final int ERROR_SPEECH_TIMEOUT = 6; // 0x6
+    field public static final int ERROR_TOO_MANY_REQUESTS = 10; // 0xa
     field public static final String RESULTS_RECOGNITION = "results_recognition";
   }
 
@@ -39207,16 +39429,22 @@
   }
 
   public static class CallScreeningService.CallResponse {
+    method public int getCallComposerAttachmentsToShow();
     method public boolean getDisallowCall();
     method public boolean getRejectCall();
     method public boolean getSilenceCall();
     method public boolean getSkipCallLog();
     method public boolean getSkipNotification();
+    field public static final int CALL_COMPOSER_ATTACHMENT_LOCATION = 2; // 0x2
+    field public static final int CALL_COMPOSER_ATTACHMENT_PICTURE = 1; // 0x1
+    field public static final int CALL_COMPOSER_ATTACHMENT_PRIORITY = 8; // 0x8
+    field public static final int CALL_COMPOSER_ATTACHMENT_SUBJECT = 4; // 0x4
   }
 
   public static class CallScreeningService.CallResponse.Builder {
     ctor public CallScreeningService.CallResponse.Builder();
     method public android.telecom.CallScreeningService.CallResponse build();
+    method @NonNull public android.telecom.CallScreeningService.CallResponse.Builder setCallComposerAttachmentsToShow(int);
     method public android.telecom.CallScreeningService.CallResponse.Builder setDisallowCall(boolean);
     method public android.telecom.CallScreeningService.CallResponse.Builder setRejectCall(boolean);
     method @NonNull public android.telecom.CallScreeningService.CallResponse.Builder setSilenceCall(boolean);
@@ -40366,6 +40594,7 @@
     field public static final String KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY = "gsm_roaming_networks_string_array";
     field public static final String KEY_HAS_IN_CALL_NOISE_SUPPRESSION_BOOL = "has_in_call_noise_suppression_bool";
     field public static final String KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL = "hide_carrier_network_settings_bool";
+    field public static final String KEY_HIDE_ENABLE_2G = "hide_enable_2g_bool";
     field public static final String KEY_HIDE_ENHANCED_4G_LTE_BOOL = "hide_enhanced_4g_lte_bool";
     field public static final String KEY_HIDE_IMS_APN_BOOL = "hide_ims_apn_bool";
     field public static final String KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL = "hide_lte_plus_data_icon_bool";
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 84e6f22..deff7b3 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -14,6 +14,7 @@
     field public static final String ACCESS_MTP = "android.permission.ACCESS_MTP";
     field public static final String ACCESS_NETWORK_CONDITIONS = "android.permission.ACCESS_NETWORK_CONDITIONS";
     field public static final String ACCESS_NOTIFICATIONS = "android.permission.ACCESS_NOTIFICATIONS";
+    field public static final String ACCESS_RCS_USER_CAPABILITY_EXCHANGE = "android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE";
     field public static final String ACCESS_SHARED_LIBRARIES = "android.permission.ACCESS_SHARED_LIBRARIES";
     field public static final String ACCESS_SHORTCUTS = "android.permission.ACCESS_SHORTCUTS";
     field public static final String ACCESS_SURFACE_FLINGER = "android.permission.ACCESS_SURFACE_FLINGER";
@@ -195,6 +196,7 @@
     field public static final String READ_DEVICE_CONFIG = "android.permission.READ_DEVICE_CONFIG";
     field public static final String READ_DREAM_STATE = "android.permission.READ_DREAM_STATE";
     field public static final String READ_INSTALL_SESSIONS = "android.permission.READ_INSTALL_SESSIONS";
+    field public static final String READ_NETWORK_DEVICE_CONFIG = "android.permission.READ_NETWORK_DEVICE_CONFIG";
     field public static final String READ_NETWORK_USAGE_HISTORY = "android.permission.READ_NETWORK_USAGE_HISTORY";
     field public static final String READ_OEM_UNLOCK_STATE = "android.permission.READ_OEM_UNLOCK_STATE";
     field public static final String READ_PEOPLE_DATA = "android.permission.READ_PEOPLE_DATA";
@@ -345,6 +347,7 @@
     field public static final int config_helpPackageNameValue = 17039388; // 0x104001c
     field public static final int config_systemAutomotiveCluster = 17039400; // 0x1040028
     field public static final int config_systemAutomotiveProjection = 17039401; // 0x1040029
+    field public static final int config_systemContacts = 17039403; // 0x104002b
     field public static final int config_systemGallery = 17039399; // 0x1040027
     field public static final int config_systemShell = 17039402; // 0x104002a
   }
@@ -892,6 +895,7 @@
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioned();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioningConfigApplied();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isManagedKiosk();
+    method public boolean isNetworkSlicingEnabledForUser(@NonNull android.os.UserHandle);
     method public boolean isSecondaryLockscreenEnabled(@NonNull android.os.UserHandle);
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isUnattendedManagedKiosk();
     method @RequiresPermission("android.permission.NOTIFY_PENDING_SYSTEM_UPDATE") public void notifyPendingSystemUpdate(long);
@@ -8959,6 +8963,16 @@
 
 package android.permission {
 
+  public final class AdminPermissionControlParams implements android.os.Parcelable {
+    method public boolean canAdminGrantSensorsPermissions();
+    method public int describeContents();
+    method public int getGrantState();
+    method @NonNull public String getGranteePackageName();
+    method @NonNull public String getPermission();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.permission.AdminPermissionControlParams> CREATOR;
+  }
+
   public final class PermissionControllerManager {
     method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.RESTORE_RUNTIME_PERMISSIONS}) public void applyStagedRuntimePermissionBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public void getRuntimePermissionBackup(@NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<byte[]>);
@@ -8990,7 +9004,8 @@
     method @Deprecated @BinderThread public void onRestoreRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable);
     method @BinderThread public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String, @NonNull Runnable);
     method @BinderThread public abstract void onRevokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull String, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.util.List<java.lang.String>>>);
-    method @BinderThread public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method @Deprecated @BinderThread public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method @BinderThread public void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull android.permission.AdminPermissionControlParams, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @BinderThread public void onStageAndApplyRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable);
     method @BinderThread public void onUpdateUserSensitivePermissionFlags(int, @NonNull java.util.concurrent.Executor, @NonNull Runnable);
     method @BinderThread public void onUpdateUserSensitivePermissionFlags(int, @NonNull Runnable);
@@ -10756,7 +10771,7 @@
     method @Nullable public android.telecom.PhoneAccountHandle getPhoneAccountHandle();
     method @Nullable public final String getTelecomCallId();
     method @Deprecated public void onAudioStateChanged(android.telecom.AudioState);
-    method @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public void onCallFilteringCompleted(boolean, boolean);
+    method @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public void onCallFilteringCompleted(boolean, boolean, @Nullable android.telecom.CallScreeningService.CallResponse, boolean);
     method public final void resetConnectionTime();
     method public void setCallDirection(int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public final void setConnectTimeMillis(@IntRange(from=0) long);
@@ -10933,7 +10948,7 @@
   }
 
   public final class RemoteConnection {
-    method @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public void onCallFilteringCompleted(boolean, boolean);
+    method @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public void onCallFilteringCompleted(boolean, boolean, @Nullable android.telecom.CallScreeningService.CallResponse, boolean);
     method @Deprecated public void setAudioState(android.telecom.AudioState);
   }
 
@@ -12012,6 +12027,7 @@
     field public static final String ACTION_SIM_CARD_STATE_CHANGED = "android.telephony.action.SIM_CARD_STATE_CHANGED";
     field public static final String ACTION_SIM_SLOT_STATUS_CHANGED = "android.telephony.action.SIM_SLOT_STATUS_CHANGED";
     field public static final int ALLOWED_NETWORK_TYPES_REASON_CARRIER = 2; // 0x2
+    field public static final int ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G = 3; // 0x3
     field public static final int ALLOWED_NETWORK_TYPES_REASON_POWER = 1; // 0x1
     field public static final int ALLOWED_NETWORK_TYPES_REASON_USER = 0; // 0x0
     field public static final int CALL_WAITING_STATUS_DISABLED = 2; // 0x2
@@ -13446,8 +13462,8 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void addOnPublishStateChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RcsUceAdapter.OnPublishStateChangedListener) throws android.telephony.ims.ImsException;
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getUcePublishState() throws android.telephony.ims.ImsException;
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void removeOnPublishStateChangedListener(@NonNull android.telephony.ims.RcsUceAdapter.OnPublishStateChangedListener) throws android.telephony.ims.ImsException;
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void requestAvailability(@NonNull android.net.Uri, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RcsUceAdapter.CapabilitiesCallback) throws android.telephony.ims.ImsException;
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void requestCapabilities(@NonNull java.util.List<android.net.Uri>, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RcsUceAdapter.CapabilitiesCallback) throws android.telephony.ims.ImsException;
+    method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE, android.Manifest.permission.READ_CONTACTS}) public void requestAvailability(@NonNull android.net.Uri, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RcsUceAdapter.CapabilitiesCallback) throws android.telephony.ims.ImsException;
+    method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE, android.Manifest.permission.READ_CONTACTS}) public void requestCapabilities(@NonNull java.util.List<android.net.Uri>, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RcsUceAdapter.CapabilitiesCallback) throws android.telephony.ims.ImsException;
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUceSettingEnabled(boolean) throws android.telephony.ims.ImsException;
     field public static final int CAPABILITY_TYPE_PRESENCE_UCE = 2; // 0x2
     field public static final int CAPABILITY_UPDATE_TRIGGER_ETAG_EXPIRED = 1; // 0x1
@@ -13726,10 +13742,16 @@
 package android.telephony.ims.stub {
 
   public interface CapabilityExchangeEventListener {
+    method public void onRemoteCapabilityRequest(@NonNull android.net.Uri, @NonNull java.util.List<java.lang.String>, @NonNull android.telephony.ims.stub.CapabilityExchangeEventListener.OptionsRequestCallback) throws android.telephony.ims.ImsException;
     method public void onRequestPublishCapabilities(int) throws android.telephony.ims.ImsException;
     method public void onUnpublish() throws android.telephony.ims.ImsException;
   }
 
+  public static interface CapabilityExchangeEventListener.OptionsRequestCallback {
+    method public default void onRespondToCapabilityRequest(@NonNull android.telephony.ims.RcsContactUceCapability, boolean);
+    method public void onRespondToCapabilityRequestWithError(@IntRange(from=100, to=699) int, @NonNull String);
+  }
+
   public interface DelegateConnectionMessageCallback {
     method public void onMessageReceived(@NonNull android.telephony.ims.SipMessage);
     method public void onMessageSendFailure(@NonNull String, int);
@@ -13916,6 +13938,7 @@
   public class RcsCapabilityExchangeImplBase {
     ctor public RcsCapabilityExchangeImplBase(@NonNull java.util.concurrent.Executor);
     method public void publishCapabilities(@NonNull String, @NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase.PublishResponseCallback);
+    method public void sendOptionsCapabilityRequest(@NonNull android.net.Uri, @NonNull java.util.List<java.lang.String>, @NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase.OptionsResponseCallback);
     method public void subscribeForCapabilities(@NonNull java.util.List<android.net.Uri>, @NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase.SubscribeResponseCallback);
     field public static final int COMMAND_CODE_FETCH_ERROR = 3; // 0x3
     field public static final int COMMAND_CODE_GENERIC_FAILURE = 1; // 0x1
@@ -13930,6 +13953,11 @@
     field public static final int COMMAND_CODE_SERVICE_UNKNOWN = 0; // 0x0
   }
 
+  public static interface RcsCapabilityExchangeImplBase.OptionsResponseCallback {
+    method public void onCommandError(int) throws android.telephony.ims.ImsException;
+    method public void onNetworkResponse(int, @NonNull String, @NonNull java.util.List<java.lang.String>) throws android.telephony.ims.ImsException;
+  }
+
   public static interface RcsCapabilityExchangeImplBase.PublishResponseCallback {
     method public void onCommandError(int) throws android.telephony.ims.ImsException;
     method public void onNetworkResponse(@IntRange(from=100, to=699) int, @NonNull String) throws android.telephony.ims.ImsException;
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index c03f660..ff96f92 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1232,11 +1232,6 @@
     method public void forceResourceLost();
   }
 
-  public final class MediaCodec implements android.media.metrics.PlaybackComponent {
-    method public String getPlaybackId();
-    method public void setPlaybackId(@NonNull String);
-  }
-
   public static final class MediaCodecInfo.VideoCapabilities.PerformancePoint {
     ctor public MediaCodecInfo.VideoCapabilities.PerformancePoint(int, int, int, int, @NonNull android.util.Size);
     ctor public MediaCodecInfo.VideoCapabilities.PerformancePoint(@NonNull android.media.MediaCodecInfo.VideoCapabilities.PerformancePoint, @NonNull android.util.Size);
@@ -1311,15 +1306,6 @@
 
 }
 
-package android.media.metrics {
-
-  public interface PlaybackComponent {
-    method @NonNull public String getPlaybackId();
-    method public void setPlaybackId(@NonNull String);
-  }
-
-}
-
 package android.media.tv {
 
   public final class TvInputManager {
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index b85b186..1906ee4 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -795,7 +795,8 @@
     // when adding one of these:
     //  - increment _NUM_OP
     //  - define an OPSTR_* constant (marked as @SystemApi)
-    //  - add rows to sOpToSwitch, sOpToString, sOpNames, sOpToPerms, sOpDefault
+    //  - add rows to sOpToSwitch, sOpToString, sOpNames, sOpPerms, sOpDefaultMode, sOpDisableReset,
+    //      sOpRestrictions, sOpAllowSystemRestrictionBypass
     //  - add descriptive strings to Settings/res/values/arrays.xml
     //  - add the op to the appropriate template in AppOpsState.OpsTemplate (settings app)
 
@@ -1174,13 +1175,19 @@
      *
      * @hide
      */
-    // TODO: Add as AppProtoEnums
-    public static final int OP_RECORD_AUDIO_OUTPUT = 106;
+    public static final int OP_RECORD_AUDIO_OUTPUT = AppProtoEnums.APP_OP_RECORD_AUDIO_OUTPUT;
+
+    /**
+     * App can schedule exact alarm to perform timing based background work
+     *
+     * @hide
+     */
+    public static final int OP_SCHEDULE_EXACT_ALARM = AppProtoEnums.APP_OP_SCHEDULE_EXACT_ALARM;
 
 
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public static final int _NUM_OP = 107;
+    public static final int _NUM_OP = 108;
 
     /** Access to coarse location information. */
     public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1553,6 +1560,13 @@
      */
     public static final String OPSTR_RECORD_AUDIO_OUTPUT = "android:record_audio_output";
 
+    /**
+     * App can schedule exact alarm to perform timing based background work.
+     *
+     * @hide
+     */
+    public static final String OPSTR_SCHEDULE_EXACT_ALARM = "android:schedule_exact_alarm";
+
     /** {@link #sAppOpsToNote} not initialized yet for this op */
     private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
     /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -1633,6 +1647,7 @@
             OP_LOADER_USAGE_STATS,
             OP_MANAGE_ONGOING_CALLS,
             OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER,
+            OP_SCHEDULE_EXACT_ALARM,
     };
 
     /**
@@ -1751,6 +1766,7 @@
             OP_MANAGE_CREDENTIALS,              // MANAGE_CREDENTIALS
             OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
             OP_RECORD_AUDIO_OUTPUT,             // RECORD_AUDIO_OUTPUT
+            OP_SCHEDULE_EXACT_ALARM,            // SCHEDULE_EXACT_ALARM
     };
 
     /**
@@ -1864,6 +1880,7 @@
             OPSTR_MANAGE_CREDENTIALS,
             OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER,
             OPSTR_RECORD_AUDIO_OUTPUT,
+            OPSTR_SCHEDULE_EXACT_ALARM,
     };
 
     /**
@@ -1978,6 +1995,7 @@
             "MANAGE_CREDENTIALS",
             "USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER",
             "RECORD_AUDIO_OUTPUT",
+            "SCHEDULE_EXACT_ALARM",
     };
 
     /**
@@ -2093,6 +2111,7 @@
             null, // no permission for OP_MANAGE_CREDENTIALS
             Manifest.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER,
             null, // no permission for OP_RECORD_AUDIO_OUTPUT
+            Manifest.permission.SCHEDULE_EXACT_ALARM,
     };
 
     /**
@@ -2208,6 +2227,7 @@
             null, // MANAGE_CREDENTIALS
             null, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
             null, // RECORD_AUDIO_OUTPUT
+            null, // SCHEDULE_EXACT_ALARM
     };
 
     /**
@@ -2322,6 +2342,7 @@
             null, // MANAGE_CREDENTIALS
             null, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
             null, // RECORD_AUDIO_OUTPUT
+            null, // SCHEDULE_EXACT_ALARM
     };
 
     /**
@@ -2435,6 +2456,7 @@
             AppOpsManager.MODE_DEFAULT, // MANAGE_CREDENTIALS
             AppOpsManager.MODE_DEFAULT, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
             AppOpsManager.MODE_ALLOWED, // RECORD_AUDIO_OUTPUT
+            AppOpsManager.MODE_DEFAULT, // SCHEDULE_EXACT_ALARM
     };
 
     /**
@@ -2552,6 +2574,7 @@
             false, // MANAGE_CREDENTIALS
             true, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
             false, // RECORD_AUDIO_OUTPUT
+            false, // SCHEDULE_EXACT_ALARM
     };
 
     /**
diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java
index 7410a1c..dfc105a 100644
--- a/core/java/android/app/ApplicationExitInfo.java
+++ b/core/java/android/app/ApplicationExitInfo.java
@@ -428,6 +428,13 @@
      */
     private IAppTraceRetriever mAppTraceRetriever;
 
+    /**
+     * ParcelFileDescriptor pointing to a native tombstone.
+     *
+     * @see #getTraceInputStream
+     */
+    private IParcelFileDescriptorRetriever mNativeTombstoneRetriever;
+
     /** @hide */
     @IntDef(prefix = { "REASON_" }, value = {
         REASON_UNKNOWN,
@@ -603,22 +610,38 @@
      * prior to the death of the process; typically it'll be available when
      * the reason is {@link #REASON_ANR}, though if the process gets an ANR
      * but recovers, and dies for another reason later, this trace will be included
-     * in the record of {@link ApplicationExitInfo} still.
+     * in the record of {@link ApplicationExitInfo} still. Beginning with API 31,
+     * tombstone traces will be returned for
+     * {@link #REASON_CRASH_NATIVE}, with an InputStream containing a protobuf with
+     * <a href="https://android.googlesource.com/platform/system/core/+/refs/heads/master/debuggerd/proto/tombstone.proto">this schema</a>.
+     * Note thatbecause these traces are kept in a separate global circular buffer, crashes may be
+     * overwritten by newer crashes (including from other applications), so this may still return
+     * null.
      *
      * @return The input stream to the traces that was taken by the system
      *         prior to the death of the process.
      */
     public @Nullable InputStream getTraceInputStream() throws IOException {
-        if (mAppTraceRetriever == null) {
+        if (mAppTraceRetriever == null && mNativeTombstoneRetriever == null) {
             return null;
         }
+
         try {
-            final ParcelFileDescriptor fd = mAppTraceRetriever.getTraceFileDescriptor(
-                    mPackageName, mPackageUid, mPid);
-            if (fd == null) {
-                return null;
+            if (mNativeTombstoneRetriever != null) {
+                final ParcelFileDescriptor pfd = mNativeTombstoneRetriever.getPfd();
+                if (pfd == null) {
+                    return null;
+                }
+
+                return new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+            } else {
+                final ParcelFileDescriptor fd = mAppTraceRetriever.getTraceFileDescriptor(
+                        mPackageName, mPackageUid, mPid);
+                if (fd == null) {
+                    return null;
+                }
+                return new GZIPInputStream(new ParcelFileDescriptor.AutoCloseInputStream(fd));
             }
-            return new GZIPInputStream(new ParcelFileDescriptor.AutoCloseInputStream(fd));
         } catch (RemoteException e) {
             return null;
         }
@@ -849,6 +872,15 @@
         mAppTraceRetriever = retriever;
     }
 
+    /**
+     * @see mNativeTombstoneRetriever
+     *
+     * @hide
+     */
+    public void setNativeTombstoneRetriever(final IParcelFileDescriptorRetriever retriever) {
+        mNativeTombstoneRetriever = retriever;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -878,6 +910,12 @@
         } else {
             dest.writeInt(0);
         }
+        if (mNativeTombstoneRetriever != null) {
+            dest.writeInt(1);
+            dest.writeStrongBinder(mNativeTombstoneRetriever.asBinder());
+        } else {
+            dest.writeInt(0);
+        }
     }
 
     /** @hide */
@@ -906,6 +944,7 @@
         mState = other.mState;
         mTraceFile = other.mTraceFile;
         mAppTraceRetriever = other.mAppTraceRetriever;
+        mNativeTombstoneRetriever = other.mNativeTombstoneRetriever;
     }
 
     private ApplicationExitInfo(@NonNull Parcel in) {
@@ -928,6 +967,10 @@
         if (in.readInt() == 1) {
             mAppTraceRetriever = IAppTraceRetriever.Stub.asInterface(in.readStrongBinder());
         }
+        if (in.readInt() == 1) {
+            mNativeTombstoneRetriever = IParcelFileDescriptorRetriever.Stub.asInterface(
+                    in.readStrongBinder());
+        }
     }
 
     public @NonNull static final Creator<ApplicationExitInfo> CREATOR =
@@ -986,6 +1029,7 @@
         sb.append(" state=").append(ArrayUtils.isEmpty(mState)
                 ? "empty" : Integer.toString(mState.length) + " bytes");
         sb.append(" trace=").append(mTraceFile);
+
         return sb.toString();
     }
 
diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java
index 96751db..033cffe 100644
--- a/core/java/android/app/EnterTransitionCoordinator.java
+++ b/core/java/android/app/EnterTransitionCoordinator.java
@@ -52,6 +52,7 @@
 
     private boolean mSharedElementTransitionStarted;
     private Activity mActivity;
+    private boolean mIsTaskRoot;
     private boolean mHasStopped;
     private boolean mIsCanceled;
     private ObjectAnimator mBackgroundAnimator;
@@ -252,7 +253,7 @@
                 cancel();
                 break;
             case MSG_ALLOW_RETURN_TRANSITION:
-                if (!mIsCanceled) {
+                if (!mIsCanceled && !mIsTaskRoot) {
                     mPendingExitNames = mAllSharedElementNames;
                 }
                 break;
@@ -343,6 +344,9 @@
         if (mActivity == null || decorView == null) {
             return;
         }
+
+        mIsTaskRoot = mActivity.isTaskRoot();
+
         if (!isCrossTask()) {
             mActivity.overridePendingTransition(0, 0);
         }
diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java
index f7097fa..cd84e56 100644
--- a/core/java/android/app/ExitTransitionCoordinator.java
+++ b/core/java/android/app/ExitTransitionCoordinator.java
@@ -551,7 +551,7 @@
 
         @Override
         public boolean isReturnTransitionAllowed() {
-            return !mActivity.isTopOfTask();
+            return true;
         }
 
         @Override
diff --git a/core/java/android/speech/tts/ITextToSpeechSession.aidl b/core/java/android/app/IParcelFileDescriptorRetriever.aidl
similarity index 61%
copy from core/java/android/speech/tts/ITextToSpeechSession.aidl
copy to core/java/android/app/IParcelFileDescriptorRetriever.aidl
index b2afeb0..7e808e7 100644
--- a/core/java/android/speech/tts/ITextToSpeechSession.aidl
+++ b/core/java/android/app/IParcelFileDescriptorRetriever.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * 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.
@@ -14,20 +14,18 @@
  * limitations under the License.
  */
 
-package android.speech.tts;
+package android.app;
+
+import android.os.ParcelFileDescriptor;
 
 /**
- * TextToSpeech session interface. Allows to control remote TTS service session once connected.
+ * An interface used to lazily provide a ParcelFileDescriptor to apps.
  *
- * @see ITextToSpeechManager
- * @see ITextToSpeechSessionCallback
- *
- * {@hide}
+ * @hide
  */
-oneway interface ITextToSpeechSession {
-
+interface IParcelFileDescriptorRetriever {
     /**
-     * Disconnects the client from the TTS provider.
+     * Retrieve the ParcelFileDescriptor.
      */
-    void disconnect();
-}
\ No newline at end of file
+    ParcelFileDescriptor getPfd();
+}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 28242b0..59e5144 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -9851,6 +9851,84 @@
     }
 
     /**
+     * Sets whether 5g slicing is enabled on the work profile.
+     *
+     * Slicing allows operators to virtually divide their networks in portions and use different
+     * portions for specific use cases; for example, a corporation can have a deal/agreement with
+     * a carrier that all of its employees’ devices use data on a slice dedicated for enterprise
+     * use.
+     *
+     * By default, 5g slicing is enabled on the work profile on supported carriers and devices.
+     * Admins can explicitly disable it with this API.
+     *
+     * <p>This method can only be called by the profile owner of a managed profile.
+     *
+     * @param enabled whether 5g Slice should be enabled.
+     * @throws SecurityException if the caller is not the profile owner.
+     **/
+    public void setNetworkSlicingEnabled(boolean enabled) {
+        throwIfParentInstance("setNetworkSlicingEnabled");
+        if (mService != null) {
+            try {
+                mService.setNetworkSlicingEnabled(enabled);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Indicates whether 5g slicing is enabled.
+     *
+     * <p>This method can be called by the profile owner of a managed profile.
+     *
+     * @return whether 5g Slice is enabled.
+     * @throws SecurityException if the caller is not the profile owner.
+     */
+    public boolean isNetworkSlicingEnabled() {
+        throwIfParentInstance("isNetworkSlicingEnabled");
+        if (mService == null) {
+            return false;
+        }
+        try {
+            return mService.isNetworkSlicingEnabled(myUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Indicates whether 5g slicing is enabled for specific user.
+     *
+     * This method can be called with permission
+     * {@link android.Manifest.permission#READ_NETWORK_DEVICE_CONFIG} by the profile owner of
+     * a managed profile. And the caller must hold the
+     * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission if query for
+     * other users.
+     *
+     * @param userHandle indicates the user to query the state
+     * @return indicates whether 5g Slice is enabled.
+     * @throws SecurityException if the caller is not granted the permission
+     *         {@link android.Manifest.permission#READ_NETWORK_DEVICE_CONFIG}
+     *         and not profile owner of a managed profile, and not granted the permission
+     *         {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} if query for
+     *         other users.
+     * @hide
+     */
+    @SystemApi
+    public boolean isNetworkSlicingEnabledForUser(@NonNull UserHandle userHandle) {
+        throwIfParentInstance("isNetworkSlicingEnabledForUser");
+        if (mService == null) {
+            return false;
+        }
+        try {
+            return mService.isNetworkSlicingEnabled(userHandle.getIdentifier());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * This method is mostly deprecated.
      * Most of the settings that still have an effect have dedicated setter methods or user
      * restrictions. See individual settings for details.
@@ -13357,6 +13435,7 @@
             }
         }
     }
+
     /**
      * Returns true if the caller is running on a device where the admin can grant
      * permissions related to device sensors.
@@ -13459,4 +13538,22 @@
         }
         return false;
     }
+
+    /**
+     * Gets the list of {@link #isAffiliatedUser() affiliated} users running on foreground.
+     *
+     * @return list of {@link #isAffiliatedUser() affiliated} users running on foreground.
+     *
+     * @throws SecurityException if the calling application is not a device owner
+     */
+    @NonNull
+    public List<UserHandle> listForegroundAffiliatedUsers() {
+        if (mService == null) return Collections.emptyList();
+
+        try {
+            return mService.listForegroundAffiliatedUsers();
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 94388cf..8a87b16 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -267,6 +267,9 @@
     void setSecondaryLockscreenEnabled(in ComponentName who, boolean enabled);
     boolean isSecondaryLockscreenEnabled(in UserHandle userHandle);
 
+    void setNetworkSlicingEnabled(in boolean enabled);
+    boolean isNetworkSlicingEnabled(int userHandle);
+
     void setLockTaskPackages(in ComponentName who, in String[] packages);
     String[] getLockTaskPackages(in ComponentName who);
     boolean isLockTaskPermitted(in String pkg);
@@ -507,4 +510,6 @@
     boolean isUsbDataSignalingEnabled(String callerPackage);
     boolean isUsbDataSignalingEnabledForUser(int userId);
     boolean canUsbDataSignalingBeDisabled();
+
+    List<UserHandle> listForegroundAffiliatedUsers();
 }
diff --git a/core/java/android/app/people/IConversationListener.aidl b/core/java/android/app/people/IConversationListener.aidl
new file mode 100644
index 0000000..7cbd66d
--- /dev/null
+++ b/core/java/android/app/people/IConversationListener.aidl
@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.people;
+
+import android.app.people.ConversationChannel;
+import android.content.pm.ParceledListSlice;
+import android.os.UserHandle;
+
+import java.util.List;
+
+/**
+ * Interface for PeopleManager#ConversationListener.
+ *
+ * @hide
+ */
+oneway interface IConversationListener
+{
+    void onConversationUpdate(in ConversationChannel conversation);
+}
\ No newline at end of file
diff --git a/core/java/android/app/people/IPeopleManager.aidl b/core/java/android/app/people/IPeopleManager.aidl
index d000f3b..496ca82 100644
--- a/core/java/android/app/people/IPeopleManager.aidl
+++ b/core/java/android/app/people/IPeopleManager.aidl
@@ -18,6 +18,7 @@
 
 import android.app.people.ConversationStatus;
 import android.app.people.ConversationChannel;
+import android.app.people.IConversationListener;
 import android.content.pm.ParceledListSlice;
 import android.net.Uri;
 import android.os.IBinder;
@@ -62,4 +63,6 @@
     void clearStatus(in String packageName, int userId, in String conversationId, in String statusId);
     void clearStatuses(in String packageName, int userId, in String conversationId);
     ParceledListSlice getStatuses(in String packageName, int userId, in String conversationId);
+    void registerConversationListener(in String packageName, int userId, in String shortcutId, in IConversationListener callback);
+    void unregisterConversationListener(in IConversationListener callback);
 }
diff --git a/core/java/android/app/people/PeopleManager.java b/core/java/android/app/people/PeopleManager.java
index d348edb..108437e 100644
--- a/core/java/android/app/people/PeopleManager.java
+++ b/core/java/android/app/people/PeopleManager.java
@@ -16,6 +16,8 @@
 
 package android.app.people;
 
+import static java.util.Objects.requireNonNull;
+
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
@@ -25,12 +27,18 @@
 import android.content.pm.ShortcutInfo;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.util.Pair;
+import android.util.Slog;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
+import java.util.concurrent.Executor;
 
 /**
  * This class allows interaction with conversation and people data.
@@ -40,11 +48,18 @@
 
     private static final String LOG_TAG = PeopleManager.class.getSimpleName();
 
-    @NonNull
-    private final Context mContext;
+    /**
+     * @hide
+     */
+    @VisibleForTesting
+    public Map<ConversationListener, Pair<Executor, IConversationListener>>
+            mConversationListeners = new HashMap<>();
 
     @NonNull
-    private final IPeopleManager mService;
+    private Context mContext;
+
+    @NonNull
+    private IPeopleManager mService;
 
     /**
      * @hide
@@ -56,6 +71,15 @@
     }
 
     /**
+     * @hide
+     */
+    @VisibleForTesting
+    public PeopleManager(@NonNull Context context, IPeopleManager service) {
+        mContext = context;
+        mService = service;
+    }
+
+    /**
      * Returns whether a shortcut has a conversation associated.
      *
      * <p>Requires android.permission.READ_PEOPLE_DATA permission.
@@ -66,9 +90,8 @@
      * clients.
      *
      * @param packageName name of the package the conversation is part of
-     * @param shortcutId the shortcut id backing the conversation
+     * @param shortcutId  the shortcut id backing the conversation
      * @return whether the {@shortcutId} is backed by a Conversation.
-     *
      * @hide
      */
     @SystemApi
@@ -94,8 +117,7 @@
      *
      * @param conversationId the {@link ShortcutInfo#getId() id} of the shortcut backing the
      *                       conversation that has an active status
-     * @param status the current status for the given conversation
-     *
+     * @param status         the current status for the given conversation
      * @return whether the role is available in the system
      */
     public void addOrUpdateStatus(@NonNull String conversationId,
@@ -115,8 +137,8 @@
      *
      * @param conversationId the {@link ShortcutInfo#getId() id} of the shortcut backing the
      *                       conversation that has an active status
-     * @param statusId the {@link ConversationStatus#getId() id} of a published status for the given
-     *                 conversation
+     * @param statusId       the {@link ConversationStatus#getId() id} of a published status for the
+     *                       given conversation
      */
     public void clearStatus(@NonNull String conversationId, @NonNull String statusId) {
         Preconditions.checkStringNotEmpty(conversationId);
@@ -155,7 +177,7 @@
         try {
             final ParceledListSlice<ConversationStatus> parceledList
                     = mService.getStatuses(
-                            mContext.getPackageName(), mContext.getUserId(), conversationId);
+                    mContext.getPackageName(), mContext.getUserId(), conversationId);
             if (parceledList != null) {
                 return parceledList.getList();
             }
@@ -164,4 +186,103 @@
         }
         return new ArrayList<>();
     }
+
+    /**
+     * Listeners for conversation changes.
+     *
+     * @hide
+     */
+    public interface ConversationListener {
+        /**
+         * Triggers when the conversation registered for a listener has been updated.
+         *
+         * @param conversation The conversation with modified data
+         * @see IPeopleManager#registerConversationListener(String, int, String,
+         * android.app.people.ConversationListener)
+         *
+         * <p>Only system root and SysUI have access to register the listener.
+         */
+        default void onConversationUpdate(@NonNull ConversationChannel conversation) {
+        }
+    }
+
+    /**
+     * Register a listener to watch for changes to the conversation identified by {@code
+     * packageName}, {@code userId}, and {@code shortcutId}.
+     *
+     * @param packageName The package name to match and filter the conversation to send updates for.
+     * @param userId      The user ID to match and filter the conversation to send updates for.
+     * @param shortcutId  The shortcut ID to match and filter the conversation to send updates for.
+     * @param listener    The listener to register to receive conversation updates.
+     * @param executor    {@link Executor} to handle the listeners. To dispatch listeners to the
+     *                    main thread of your application, you can use
+     *                    {@link android.content.Context#getMainExecutor()}.
+     * @hide
+     */
+    public void registerConversationListener(String packageName, int userId, String shortcutId,
+            ConversationListener listener, Executor executor) {
+        requireNonNull(listener, "Listener cannot be null");
+        requireNonNull(packageName, "Package name cannot be null");
+        requireNonNull(shortcutId, "Shortcut ID cannot be null");
+        synchronized (mConversationListeners) {
+            IConversationListener proxy = (IConversationListener) new ConversationListenerProxy(
+                    executor, listener);
+            try {
+                mService.registerConversationListener(
+                        packageName, userId, shortcutId, proxy);
+                mConversationListeners.put(listener,
+                        new Pair<>(executor, proxy));
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Unregisters the listener previously registered to watch conversation changes.
+     *
+     * @param listener The listener to register to receive conversation updates.
+     * @hide
+     */
+    public void unregisterConversationListener(
+            ConversationListener listener) {
+        requireNonNull(listener, "Listener cannot be null");
+
+        synchronized (mConversationListeners) {
+            if (mConversationListeners.containsKey(listener)) {
+                IConversationListener proxy = mConversationListeners.remove(listener).second;
+                try {
+                    mService.unregisterConversationListener(proxy);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+        }
+    }
+
+    /**
+     * Listener proxy class for {@link ConversationListener}
+     *
+     * @hide
+     */
+    private static class ConversationListenerProxy extends
+            IConversationListener.Stub {
+        private final Executor mExecutor;
+        private final ConversationListener mListener;
+
+        ConversationListenerProxy(Executor executor, ConversationListener listener) {
+            mExecutor = executor;
+            mListener = listener;
+        }
+
+        @Override
+        public void onConversationUpdate(@NonNull ConversationChannel conversation) {
+            if (mListener == null || mExecutor == null) {
+                // Binder is dead.
+                Slog.e(LOG_TAG, "Binder is dead");
+                return;
+            }
+            mExecutor.execute(() -> mListener.onConversationUpdate(conversation));
+        }
+    }
 }
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 10b00f2..5166943 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4583,14 +4583,6 @@
     public static final String AUTOFILL_MANAGER_SERVICE = "autofill";
 
     /**
-     * Official published name of the (internal) text to speech manager service.
-     *
-     * @hide
-     * @see #getSystemService(String)
-     */
-    public static final String TEXT_TO_SPEECH_MANAGER_SERVICE = "texttospeech";
-
-    /**
      * Official published name of the content capture service.
      *
      * @hide
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index f9980bc..567501c 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -2065,6 +2065,13 @@
             this.forceQueryableOverride = true;
         }
 
+        /**
+         * Sets the install scenario for this session, which describes the expected user journey.
+         */
+        public void setInstallScenario(@InstallScenario int installScenario) {
+            this.installScenario = installScenario;
+        }
+
         /** {@hide} */
         public void dump(IndentingPrintWriter pw) {
             pw.printPair("mode", mode);
@@ -2224,7 +2231,7 @@
         /** {@hide} */
         public @InstallReason int installReason;
         /** {@hide} */
-        public @InstallReason int installScenario;
+        public @InstallScenario int installScenario;
         /** {@hide} */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
         public long sizeBytes;
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index a3c3500..fdb00c6 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1356,15 +1356,11 @@
 
     /**
      * A value to indicate the lack of CUJ information, disabling all installation scenario logic.
-     *
-     * @hide
      */
     public static final int INSTALL_SCENARIO_DEFAULT = 0;
 
     /**
      * Installation scenario providing the fastest “install button to launch" experience possible.
-     *
-     * @hide
      */
     public static final int INSTALL_SCENARIO_FAST = 1;
 
@@ -1381,8 +1377,6 @@
      * less optimized applications.  The device state (e.g. memory usage or battery status) should
      * not be considered when making this decision as those factors are taken into account by the
      * Package Manager when acting on the installation scenario.
-     *
-     * @hide
      */
     public static final int INSTALL_SCENARIO_BULK = 2;
 
@@ -1393,8 +1387,6 @@
      * operation that are marked BULK_SECONDARY, the faster the entire bulk operation will be.
      *
      * See the comments for INSTALL_SCENARIO_BULK for more information.
-     *
-     * @hide
      */
     public static final int INSTALL_SCENARIO_BULK_SECONDARY = 3;
 
@@ -3677,6 +3669,15 @@
     public static final String FEATURE_KEYSTORE_LIMITED_USE_KEY =
             "android.hardware.keystore.limited_use_key";
 
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
+     * a Keystore implementation that can create application-specific attestation keys.
+     * See {@link android.security.keystore.KeyGenParameterSpec.Builder#setAttestKeyAlias}.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_KEYSTORE_APP_ATTEST_KEY =
+            "android.hardware.keystore.app_attest_key";
+
     /** @hide */
     public static final boolean APP_ENUMERATION_ENABLED_BY_DEFAULT = true;
 
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index 5f5697a..5b28e00 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -237,7 +237,8 @@
     public BiometricTestSession createTestSession(int sensorId) {
         try {
             return new BiometricTestSession(mContext, sensorId,
-                    mService.createTestSession(sensorId, mContext.getOpPackageName()));
+                    (context, sensorId1, callback) -> mService
+                            .createTestSession(sensorId1, callback, context.getOpPackageName()));
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -390,7 +391,6 @@
      * in Keystore land as SIDs, and are used during key generation.
      * @hide
      */
-    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
     public long[] getAuthenticatorIds() {
         if (mService != null) {
             try {
diff --git a/core/java/android/hardware/biometrics/BiometricTestSession.java b/core/java/android/hardware/biometrics/BiometricTestSession.java
index 1c35608..ff1a17e 100644
--- a/core/java/android/hardware/biometrics/BiometricTestSession.java
+++ b/core/java/android/hardware/biometrics/BiometricTestSession.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.TEST_BIOMETRIC;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.TestApi;
 import android.content.Context;
@@ -27,6 +28,9 @@
 import android.util.ArraySet;
 import android.util.Log;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
 /**
  * Common set of interfaces to test biometric-related APIs, including {@link BiometricPrompt} and
  * {@link android.hardware.fingerprint.FingerprintManager}.
@@ -36,22 +40,58 @@
 public class BiometricTestSession implements AutoCloseable {
     private static final String TAG = "BiometricTestSession";
 
+    /**
+     * @hide
+     */
+    public interface TestSessionProvider {
+        @NonNull
+        ITestSession createTestSession(@NonNull Context context, int sensorId,
+                @NonNull ITestSessionCallback callback) throws RemoteException;
+    }
+
     private final Context mContext;
     private final int mSensorId;
     private final ITestSession mTestSession;
 
     // Keep track of users that were tested, which need to be cleaned up when finishing.
-    private final ArraySet<Integer> mTestedUsers;
+    @NonNull private final ArraySet<Integer> mTestedUsers;
+
+    // Track the users currently cleaning up, and provide a latch that gets notified when all
+    // users have finished cleaning up. This is an imperfect system, as there can technically be
+    // multiple cleanups per user. Theoretically we should track the cleanup's BaseClientMonitor's
+    // unique ID, but it's complicated to plumb it through. This should be fine for now.
+    @Nullable private CountDownLatch mCloseLatch;
+    @NonNull private final ArraySet<Integer> mUsersCleaningUp;
+
+    private final ITestSessionCallback mCallback = new ITestSessionCallback.Stub() {
+        @Override
+        public void onCleanupStarted(int userId) {
+            Log.d(TAG, "onCleanupStarted, sensor: " + mSensorId + ", userId: " + userId);
+        }
+
+        @Override
+        public void onCleanupFinished(int userId) {
+            Log.d(TAG, "onCleanupFinished, sensor: " + mSensorId
+                    + ", userId: " + userId
+                    + ", remaining users: " + mUsersCleaningUp.size());
+            mUsersCleaningUp.remove(userId);
+
+            if (mUsersCleaningUp.isEmpty() && mCloseLatch != null) {
+                mCloseLatch.countDown();
+            }
+        }
+    };
 
     /**
      * @hide
      */
     public BiometricTestSession(@NonNull Context context, int sensorId,
-            @NonNull ITestSession testSession) {
+            @NonNull TestSessionProvider testSessionProvider) throws RemoteException {
         mContext = context;
         mSensorId = sensorId;
-        mTestSession = testSession;
+        mTestSession = testSessionProvider.createTestSession(context, sensorId, mCallback);
         mTestedUsers = new ArraySet<>();
+        mUsersCleaningUp = new ArraySet<>();
         setTestHalEnabled(true);
     }
 
@@ -176,6 +216,11 @@
     @RequiresPermission(TEST_BIOMETRIC)
     public void cleanupInternalState(int userId) {
         try {
+            if (mUsersCleaningUp.contains(userId)) {
+                Log.w(TAG, "Cleanup already in progress for user: " + userId);
+            }
+
+            mUsersCleaningUp.add(userId);
             mTestSession.cleanupInternalState(userId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -185,12 +230,24 @@
     @Override
     @RequiresPermission(TEST_BIOMETRIC)
     public void close() {
-        // Disable the test HAL first, so that enumerate is run on the real HAL, which should have
-        // no enrollments. Test-only framework enrollments will be deleted.
-        setTestHalEnabled(false);
+        // Cleanup can be performed using the test HAL, since it always responds to enumerate with
+        // zero enrollments.
+        if (!mTestedUsers.isEmpty()) {
+            mCloseLatch = new CountDownLatch(1);
+            for (int user : mTestedUsers) {
+                cleanupInternalState(user);
+            }
 
-        for (int user : mTestedUsers) {
-            cleanupInternalState(user);
+            try {
+                Log.d(TAG, "Awaiting latch...");
+                mCloseLatch.await(10, TimeUnit.SECONDS);
+                Log.d(TAG, "Finished awaiting");
+            } catch (InterruptedException e) {
+                Log.e(TAG, "Latch interrupted", e);
+            }
         }
+
+        // Disable the test HAL after the sensor becomes idle.
+        setTestHalEnabled(false);
     }
 }
diff --git a/core/java/android/hardware/biometrics/IAuthService.aidl b/core/java/android/hardware/biometrics/IAuthService.aidl
index 0dfd5db..d8c9dbc 100644
--- a/core/java/android/hardware/biometrics/IAuthService.aidl
+++ b/core/java/android/hardware/biometrics/IAuthService.aidl
@@ -20,6 +20,7 @@
 import android.hardware.biometrics.IBiometricServiceReceiver;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.biometrics.SensorPropertiesInternal;
 
@@ -32,7 +33,7 @@
  */
 interface IAuthService {
     // Creates a test session with the specified sensorId
-    ITestSession createTestSession(int sensorId, String opPackageName);
+    ITestSession createTestSession(int sensorId, ITestSessionCallback callback, String opPackageName);
 
     // Retrieve static sensor properties for all biometric sensors
     List<SensorPropertiesInternal> getSensorProperties(String opPackageName);
diff --git a/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
index c854ac98..7639c5d 100644
--- a/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
@@ -20,6 +20,7 @@
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.SensorPropertiesInternal;
 import android.hardware.face.IFaceServiceReceiver;
 import android.hardware.face.Face;
@@ -32,7 +33,7 @@
 interface IBiometricAuthenticator {
 
     // Creates a test session
-    ITestSession createTestSession(String opPackageName);
+    ITestSession createTestSession(ITestSessionCallback callback, String opPackageName);
 
     // Retrieve static sensor properties
     SensorPropertiesInternal getSensorProperties(String opPackageName);
diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl
index a14a910..2433186 100644
--- a/core/java/android/hardware/biometrics/IBiometricService.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricService.aidl
@@ -21,6 +21,7 @@
 import android.hardware.biometrics.IBiometricAuthenticator;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.biometrics.SensorPropertiesInternal;
 
@@ -30,7 +31,7 @@
  */
 interface IBiometricService {
     // Creates a test session with the specified sensorId
-    ITestSession createTestSession(int sensorId, String opPackageName);
+    ITestSession createTestSession(int sensorId, ITestSessionCallback callback, String opPackageName);
 
     // Retrieve static sensor properties for all biometric sensors
     List<SensorPropertiesInternal> getSensorProperties(String opPackageName);
diff --git a/core/java/android/hardware/biometrics/ITestSession.aidl b/core/java/android/hardware/biometrics/ITestSession.aidl
index fa7a62c..f8395a1 100644
--- a/core/java/android/hardware/biometrics/ITestSession.aidl
+++ b/core/java/android/hardware/biometrics/ITestSession.aidl
@@ -18,7 +18,7 @@
 import android.hardware.biometrics.SensorPropertiesInternal;
 
 /**
- * A test service for FingerprintManager and BiometricPrompt.
+ * A test service for FingerprintManager and BiometricManager.
  * @hide
  */
 interface ITestSession {
diff --git a/core/java/android/hardware/biometrics/ITestSessionCallback.aidl b/core/java/android/hardware/biometrics/ITestSessionCallback.aidl
new file mode 100644
index 0000000..3d9517f
--- /dev/null
+++ b/core/java/android/hardware/biometrics/ITestSessionCallback.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.hardware.biometrics;
+
+/**
+ * ITestSession callback for FingerprintManager and BiometricManager.
+ * @hide
+ */
+interface ITestSessionCallback {
+    void onCleanupStarted(int userId);
+    void onCleanupFinished(int userId);
+}
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 0f595b7..16ab900 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -2908,6 +2908,29 @@
             new Key<int[]>("android.scaler.availableRotateAndCropModes", int[].class);
 
     /**
+     * <p>Default YUV/PRIVATE size to use for requesting secure image buffers.</p>
+     * <p>This entry lists the default size supported in the secure camera mode. This entry is
+     * optional on devices support the SECURE_IMAGE_DATA capability. This entry will be null
+     * if the camera device does not list SECURE_IMAGE_DATA capability.</p>
+     * <p>When the key is present, only a PRIVATE/YUV output of the specified size is guaranteed
+     * to be supported by the camera HAL in the secure camera mode. Any other format or
+     * resolutions might not be supported. Use
+     * {@link CameraDevice#isSessionConfigurationSupported }
+     * API to query if a secure session configuration is supported if the device supports this
+     * API.</p>
+     * <p>If this key returns null on a device with SECURE_IMAGE_DATA capability, the application
+     * can assume all output sizes listed in the
+     * {@link android.hardware.camera2.params.StreamConfigurationMap }
+     * are supported.</p>
+     * <p><b>Units</b>: Pixels</p>
+     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     */
+    @PublicKey
+    @NonNull
+    public static final Key<android.util.Size> SCALER_DEFAULT_SECURE_IMAGE_SIZE =
+            new Key<android.util.Size>("android.scaler.defaultSecureImageSize", android.util.Size.class);
+
+    /**
      * <p>The area of the image sensor which corresponds to active pixels after any geometric
      * distortion correction has been applied.</p>
      * <p>This is the rectangle representing the size of the active region of the sensor (i.e.
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 886a8c1..a9bcdeff 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -574,12 +574,23 @@
                 mService.remove(mToken, face.getBiometricId(), userId, mServiceReceiver,
                         mContext.getOpPackageName());
             } catch (RemoteException e) {
-                Slog.w(TAG, "Remote exception in remove: ", e);
-                if (callback != null) {
-                    callback.onRemovalError(face, FACE_ERROR_HW_UNAVAILABLE,
-                            getErrorString(mContext, FACE_ERROR_HW_UNAVAILABLE,
-                                0 /* vendorCode */));
-                }
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Removes all face templates for the given user.
+     * @hide
+     */
+    @RequiresPermission(MANAGE_BIOMETRIC)
+    public void removeAll(int userId, @NonNull RemovalCallback callback) {
+        if (mService != null) {
+            try {
+                mRemovalCallback = callback;
+                mService.removeAll(mToken, userId, mServiceReceiver, mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
             }
         }
     }
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index a3e7e2d..6e7c701 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -19,6 +19,7 @@
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.face.IFaceServiceReceiver;
 import android.hardware.face.Face;
 import android.hardware.face.FaceSensorPropertiesInternal;
@@ -32,7 +33,7 @@
 interface IFaceService {
 
     // Creates a test session with the specified sensorId
-    ITestSession createTestSession(int sensorId, String opPackageName);
+    ITestSession createTestSession(int sensorId, ITestSessionCallback callback, String opPackageName);
 
     // Requests a proto dump of the specified sensor
     byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer);
@@ -83,10 +84,13 @@
     // Cancel enrollment in progress
     void cancelEnrollment(IBinder token);
 
-    // Any errors resulting from this call will be returned to the listener
+    // Removes the specified face enrollment for the specified userId.
     void remove(IBinder token, int faceId, int userId, IFaceServiceReceiver receiver,
             String opPackageName);
 
+    // Removes all face enrollments for the specified userId.
+    void removeAll(IBinder token, int userId, IFaceServiceReceiver receiver, String opPackageName);
+
     // Get the enrolled face for user.
     List<Face> getEnrolledFaces(int sensorId, int userId, String opPackageName);
 
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index a614ebf..9d086cf 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -154,7 +154,8 @@
     public BiometricTestSession createTestSession(int sensorId) {
         try {
             return new BiometricTestSession(mContext, sensorId,
-                    mService.createTestSession(sensorId, mContext.getOpPackageName()));
+                    (context, sensorId1, callback) -> mService
+                            .createTestSession(sensorId1, callback, context.getOpPackageName()));
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -739,11 +740,22 @@
             mService.remove(mToken, fp.getBiometricId(), userId, mServiceReceiver,
                     mContext.getOpPackageName());
         } catch (RemoteException e) {
-            Slog.w(TAG, "Remote exception in remove: ", e);
-            if (callback != null) {
-                callback.onRemovalError(fp, FINGERPRINT_ERROR_HW_UNAVAILABLE,
-                        getErrorString(mContext, FINGERPRINT_ERROR_HW_UNAVAILABLE,
-                            0 /* vendorCode */));
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Removes all face templates for the given user.
+     * @hide
+     */
+    @RequiresPermission(MANAGE_FINGERPRINT)
+    public void removeAll(int userId, @NonNull RemovalCallback callback) {
+        if (mService != null) {
+            try {
+                mRemovalCallback = callback;
+                mService.removeAll(mToken, userId, mServiceReceiver, mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
             }
         }
     }
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index 8888247..054c0d0 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -19,6 +19,7 @@
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.fingerprint.IFingerprintClientActiveCallback;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
 import android.hardware.fingerprint.IUdfpsOverlayController;
@@ -33,7 +34,7 @@
 interface IFingerprintService {
 
     // Creates a test session with the specified sensorId
-    ITestSession createTestSession(int sensorId, String opPackageName);
+    ITestSession createTestSession(int sensorId, ITestSessionCallback callback, String opPackageName);
 
     // Requests a proto dump of the specified sensor
     byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer);
@@ -87,6 +88,9 @@
     void remove(IBinder token, int fingerId, int userId, IFingerprintServiceReceiver receiver,
             String opPackageName);
 
+    // Removes all face enrollments for the specified userId.
+    void removeAll(IBinder token, int userId, IFingerprintServiceReceiver receiver, String opPackageName);
+
     // Rename the fingerprint specified by fingerId and userId to the given name
     void rename(int fingerId, int userId, String name);
 
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index ebb3021..a65f36b 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -24,6 +24,7 @@
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.app.ActivityThread;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
@@ -849,9 +850,18 @@
             attributionTag = context.getAttributionTag();
         }
 
+        // Workaround for old APIs not providing a context
+        String packageName;
+        if (context != null) {
+            packageName = context.getPackageName();
+        } else {
+            packageName = ActivityThread.currentPackageName();
+        }
+
         IContextHubClient clientProxy;
         try {
-            clientProxy = mService.createClient(hubInfo.getId(), clientInterface, attributionTag);
+            clientProxy = mService.createClient(
+                    hubInfo.getId(), clientInterface, attributionTag, packageName);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/hardware/location/IContextHubService.aidl b/core/java/android/hardware/location/IContextHubService.aidl
index 4961195..92882c4 100644
--- a/core/java/android/hardware/location/IContextHubService.aidl
+++ b/core/java/android/hardware/location/IContextHubService.aidl
@@ -60,7 +60,8 @@
 
     // Creates a client to send and receive messages
     IContextHubClient createClient(
-            int contextHubId, in IContextHubClientCallback client, in String attributionTag);
+            int contextHubId, in IContextHubClientCallback client, in String attributionTag,
+            in String packageName);
 
     // Creates a PendingIntent-based client to send and receive messages
     IContextHubClient createPendingIntentClient(
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityMetricsEvent.aidl b/core/java/android/net/ConnectivityMetricsEvent.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/ConnectivityMetricsEvent.aidl
rename to core/java/android/net/ConnectivityMetricsEvent.aidl
diff --git a/packages/Connectivity/framework/src/android/net/InterfaceConfiguration.aidl b/core/java/android/net/InterfaceConfiguration.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/InterfaceConfiguration.aidl
rename to core/java/android/net/InterfaceConfiguration.aidl
diff --git a/packages/Connectivity/framework/src/android/net/UidRange.aidl b/core/java/android/net/UidRange.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/UidRange.aidl
rename to core/java/android/net/UidRange.aidl
diff --git a/core/java/android/net/vcn/IVcnManagementService.aidl b/core/java/android/net/vcn/IVcnManagementService.aidl
index 4f293ee..6a3cb42 100644
--- a/core/java/android/net/vcn/IVcnManagementService.aidl
+++ b/core/java/android/net/vcn/IVcnManagementService.aidl
@@ -18,6 +18,7 @@
 
 import android.net.LinkProperties;
 import android.net.NetworkCapabilities;
+import android.net.vcn.IVcnStatusCallback;
 import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
 import android.net.vcn.VcnConfig;
 import android.net.vcn.VcnUnderlyingNetworkPolicy;
@@ -33,4 +34,7 @@
     void addVcnUnderlyingNetworkPolicyListener(in IVcnUnderlyingNetworkPolicyListener listener);
     void removeVcnUnderlyingNetworkPolicyListener(in IVcnUnderlyingNetworkPolicyListener listener);
     VcnUnderlyingNetworkPolicy getUnderlyingNetworkPolicy(in NetworkCapabilities nc, in LinkProperties lp);
+
+    void registerVcnStatusCallback(in ParcelUuid subscriptionGroup, in IVcnStatusCallback callback, in String opPkgName);
+    void unregisterVcnStatusCallback(in IVcnStatusCallback callback);
 }
diff --git a/core/java/android/net/vcn/IVcnStatusCallback.aidl b/core/java/android/net/vcn/IVcnStatusCallback.aidl
new file mode 100644
index 0000000..555e9b5
--- /dev/null
+++ b/core/java/android/net/vcn/IVcnStatusCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vcn;
+
+/** @hide */
+interface IVcnStatusCallback {
+    void onEnteredSafeMode();
+    void onGatewayConnectionError(
+            in int[] gatewayNetworkCapabilities,
+            int errorCode,
+            in String exceptionClass,
+            in String exceptionMessage);
+}
\ No newline at end of file
diff --git a/core/java/android/net/vcn/VcnConfig.java b/core/java/android/net/vcn/VcnConfig.java
index 5eb4ba6..52cc218 100644
--- a/core/java/android/net/vcn/VcnConfig.java
+++ b/core/java/android/net/vcn/VcnConfig.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.net.NetworkRequest;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.PersistableBundle;
@@ -41,7 +42,6 @@
  * brought up on demand based on active {@link NetworkRequest}(s).
  *
  * @see VcnManager for more information on the Virtual Carrier Network feature
- * @hide
  */
 public final class VcnConfig implements Parcelable {
     @NonNull private static final String TAG = VcnConfig.class.getSimpleName();
@@ -56,7 +56,8 @@
             @NonNull String packageName,
             @NonNull Set<VcnGatewayConnectionConfig> gatewayConnectionConfigs) {
         mPackageName = packageName;
-        mGatewayConnectionConfigs = Collections.unmodifiableSet(gatewayConnectionConfigs);
+        mGatewayConnectionConfigs =
+                Collections.unmodifiableSet(new ArraySet<>(gatewayConnectionConfigs));
 
         validate();
     }
@@ -96,11 +97,7 @@
         return mPackageName;
     }
 
-    /**
-     * Retrieves the set of configured tunnels.
-     *
-     * @hide
-     */
+    /** Retrieves the set of configured GatewayConnection(s). */
     @NonNull
     public Set<VcnGatewayConnectionConfig> getGatewayConnectionConfigs() {
         return Collections.unmodifiableSet(mGatewayConnectionConfigs);
@@ -168,11 +165,7 @@
                 }
             };
 
-    /**
-     * This class is used to incrementally build {@link VcnConfig} objects.
-     *
-     * @hide
-     */
+    /** This class is used to incrementally build {@link VcnConfig} objects. */
     public static final class Builder {
         @NonNull private final String mPackageName;
 
@@ -190,7 +183,6 @@
          *
          * @param gatewayConnectionConfig the configuration for an individual gateway connection
          * @return this {@link Builder} instance, for chaining
-         * @hide
          */
         @NonNull
         public Builder addGatewayConnectionConfig(
@@ -205,7 +197,6 @@
          * Builds and validates the VcnConfig.
          *
          * @return an immutable VcnConfig instance
-         * @hide
          */
         @NonNull
         public VcnConfig build() {
diff --git a/core/java/android/net/vcn/VcnControlPlaneConfig.java b/core/java/android/net/vcn/VcnControlPlaneConfig.java
new file mode 100644
index 0000000..0c6ccfe
--- /dev/null
+++ b/core/java/android/net/vcn/VcnControlPlaneConfig.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.vcn;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.PersistableBundle;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * This class represents a control plane configuration for a Virtual Carrier Network connection.
+ *
+ * <p>Each {@link VcnGatewayConnectionConfig} must have a {@link VcnControlPlaneConfig}, containing
+ * all connection, authentication and authorization parameters required to establish a Gateway
+ * Connection with a remote endpoint.
+ *
+ * <p>A {@link VcnControlPlaneConfig} object can be shared by multiple {@link
+ * VcnGatewayConnectionConfig}(s) if they will used for connecting with the same remote endpoint.
+ *
+ * @see VcnManager
+ * @see VcnGatewayConnectionConfig
+ *
+ * @hide
+ */
+public abstract class VcnControlPlaneConfig {
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({CONFIG_TYPE_IKE})
+    public @interface ConfigType {}
+
+    /** @hide */
+    public static final int CONFIG_TYPE_IKE = 1;
+
+    private static final String CONFIG_TYPE_KEY = "mConfigType";
+    @ConfigType private final int mConfigType;
+
+    /**
+     * Package private constructor.
+     *
+     * @hide
+     */
+    VcnControlPlaneConfig(int configType) {
+        mConfigType = configType;
+    }
+
+    /**
+     * Constructs a VcnControlPlaneConfig object by deserializing a PersistableBundle.
+     *
+     * @param in the {@link PersistableBundle} containing an {@link VcnControlPlaneConfig} object
+     * @hide
+     */
+    public static VcnControlPlaneConfig fromPersistableBundle(@NonNull PersistableBundle in) {
+        Objects.requireNonNull(in, "PersistableBundle was null");
+
+        int configType = in.getInt(CONFIG_TYPE_KEY);
+        switch (configType) {
+            case CONFIG_TYPE_IKE:
+                return new VcnControlPlaneIkeConfig(in);
+            default:
+                throw new IllegalStateException("Unrecognized configType: " + configType);
+        }
+    }
+
+    /**
+     * Converts this VcnControlPlaneConfig to a PersistableBundle.
+     *
+     * @hide
+     */
+    @NonNull
+    public PersistableBundle toPersistableBundle() {
+        final PersistableBundle result = new PersistableBundle();
+        result.putInt(CONFIG_TYPE_KEY, mConfigType);
+        return result;
+    }
+
+    /** @hide */
+    @Override
+    public int hashCode() {
+        return Objects.hash(mConfigType);
+    }
+
+    /** @hide */
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof VcnControlPlaneConfig)) {
+            return false;
+        }
+
+        return mConfigType == ((VcnControlPlaneConfig) o).mConfigType;
+    }
+
+    /**
+     * Returns a deep copy of this object.
+     *
+     * @hide
+     */
+    public abstract VcnControlPlaneConfig copy();
+}
diff --git a/core/java/android/net/vcn/VcnControlPlaneIkeConfig.java b/core/java/android/net/vcn/VcnControlPlaneIkeConfig.java
new file mode 100644
index 0000000..2f6e1f6
--- /dev/null
+++ b/core/java/android/net/vcn/VcnControlPlaneIkeConfig.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vcn;
+
+import static android.net.vcn.VcnControlPlaneConfig.CONFIG_TYPE_IKE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.ipsec.ike.IkeSessionParams;
+import android.net.ipsec.ike.TunnelModeChildSessionParams;
+import android.os.PersistableBundle;
+import android.util.ArraySet;
+
+import java.util.Objects;
+
+/**
+ * This class is an IKEv2 control plane configuration for a Virtual Carrier Network connection.
+ *
+ * <p>This class is an extension of the {@link VcnControlPlaneConfig}, containing IKEv2-specific
+ * configuration, authentication and authorization parameters.
+ *
+ * @see VcnControlPlaneConfig
+ *
+ * @hide
+ */
+public final class VcnControlPlaneIkeConfig extends VcnControlPlaneConfig {
+    private static final String TAG = VcnControlPlaneIkeConfig.class.getSimpleName();
+
+    // STOPSHIP: b/163604823 Make mIkeParams and mChildParams @NonNull when it is supported to
+    // construct mIkeParams and mChildParams from PersistableBundles.
+
+    private static final String IKE_PARAMS_KEY = "mIkeParams";
+    @Nullable private final IkeSessionParams mIkeParams;
+
+    private static final String CHILD_PARAMS_KEY = "mChildParams";
+    @Nullable private final TunnelModeChildSessionParams mChildParams;
+
+    private static final ArraySet<String> BUNDLE_KEY_SET = new ArraySet<>();
+
+    {
+        BUNDLE_KEY_SET.add(IKE_PARAMS_KEY);
+        BUNDLE_KEY_SET.add(CHILD_PARAMS_KEY);
+    }
+
+    /**
+     * Constructs a VcnControlPlaneIkeConfig object.
+     *
+     * @param ikeParams the IKE Session negotiation parameters
+     * @param childParams the tunnel mode Child Session negotiation parameters
+     */
+    public VcnControlPlaneIkeConfig(
+            @NonNull IkeSessionParams ikeParams,
+            @NonNull TunnelModeChildSessionParams childParams) {
+        super(CONFIG_TYPE_IKE);
+        mIkeParams = ikeParams;
+        mChildParams = childParams;
+        validate();
+    }
+
+    /**
+     * Constructs a VcnControlPlaneIkeConfig object by deserializing a PersistableBundle.
+     *
+     * @param in the {@link PersistableBundle} containing an {@link VcnControlPlaneIkeConfig} object
+     * @hide
+     */
+    public VcnControlPlaneIkeConfig(@NonNull PersistableBundle in) {
+        super(CONFIG_TYPE_IKE);
+        final PersistableBundle ikeParamsBundle = in.getPersistableBundle(IKE_PARAMS_KEY);
+        final PersistableBundle childParamsBundle = in.getPersistableBundle(CHILD_PARAMS_KEY);
+
+        // STOPSHIP: b/163604823 Support constructing mIkeParams and mChildParams from
+        // PersistableBundles.
+
+        mIkeParams = null;
+        mChildParams = null;
+    }
+
+    private void validate() {
+        Objects.requireNonNull(mIkeParams, "mIkeParams was null");
+        Objects.requireNonNull(mChildParams, "mChildParams was null");
+    }
+
+    /**
+     * Converts this VcnControlPlaneConfig to a PersistableBundle.
+     *
+     * @hide
+     */
+    @Override
+    @NonNull
+    public PersistableBundle toPersistableBundle() {
+        final PersistableBundle result = super.toPersistableBundle();
+
+        // STOPSHIP: b/163604823 Support converting mIkeParams and mChildParams to
+        // PersistableBundles.
+        return result;
+    }
+
+    /** Retrieves the IKE Session configuration. */
+    @NonNull
+    public IkeSessionParams getIkeSessionParams() {
+        return mIkeParams;
+    }
+
+    /** Retrieves the tunnel mode Child Session configuration. */
+    @NonNull
+    public TunnelModeChildSessionParams getChildSessionParams() {
+        return mChildParams;
+    }
+
+    /** @hide */
+    @Override
+    public int hashCode() {
+        return Objects.hash(super.hashCode(), mIkeParams, mChildParams);
+    }
+
+    /** @hide */
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof VcnControlPlaneIkeConfig)) {
+            return false;
+        }
+
+        VcnControlPlaneIkeConfig other = (VcnControlPlaneIkeConfig) o;
+
+        // STOPSHIP: b/163604823 Also check mIkeParams and mChildParams when it is supported to
+        // construct mIkeParams and mChildParams from PersistableBundles. They are not checked
+        // now so that VcnGatewayConnectionConfigTest and VcnConfigTest can pass.
+        return super.equals(o);
+    }
+
+    /** @hide */
+    @Override
+    public VcnControlPlaneConfig copy() {
+        return new VcnControlPlaneIkeConfig(
+                new IkeSessionParams.Builder(mIkeParams).build(),
+                new TunnelModeChildSessionParams.Builder(mChildParams).build());
+    }
+}
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
index cead2f1..94dff91 100644
--- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
+++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -21,6 +21,8 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.os.PersistableBundle;
 import android.util.ArraySet;
@@ -55,28 +57,23 @@
  * subscription group under which this configuration is registered (see {@link
  * VcnManager#setVcnConfig}).
  *
- * <p>Services that can be provided by a VCN network, or required for underlying networks are
- * limited to services provided by cellular networks:
+ * <p>As an abstraction of a cellular network, services that can be provided by a VCN network, or
+ * required for underlying networks are limited to services provided by cellular networks:
  *
  * <ul>
- *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_MMS}
- *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_SUPL}
- *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_DUN}
- *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_FOTA}
- *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_IMS}
- *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_CBS}
- *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_IA}
- *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_RCS}
- *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_XCAP}
- *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_EIMS}
- *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET}
- *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_MCX}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_MMS}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_SUPL}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_DUN}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_FOTA}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_IMS}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_CBS}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_IA}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_RCS}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_XCAP}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_EIMS}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_INTERNET}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_MCX}
  * </ul>
- *
- * <p>The meteredness and roaming of the VCN {@link Network} will be determined by that of the
- * underlying Network(s).
- *
- * @hide
  */
 public final class VcnGatewayConnectionConfig {
     // TODO: Use MIN_MTU_V6 once it is public, @hide
@@ -153,14 +150,15 @@
                 TimeUnit.MINUTES.toMillis(15)
             };
 
+    private static final String CTRL_PLANE_CONFIG_KEY = "mCtrlPlaneConfig";
+    @NonNull private VcnControlPlaneConfig mCtrlPlaneConfig;
+
     private static final String EXPOSED_CAPABILITIES_KEY = "mExposedCapabilities";
     @NonNull private final SortedSet<Integer> mExposedCapabilities;
 
     private static final String UNDERLYING_CAPABILITIES_KEY = "mUnderlyingCapabilities";
     @NonNull private final SortedSet<Integer> mUnderlyingCapabilities;
 
-    // TODO: Add Ike/ChildSessionParams as a subclass - maybe VcnIkeGatewayConnectionConfig
-
     private static final String MAX_MTU_KEY = "mMaxMtu";
     private final int mMaxMtu;
 
@@ -169,10 +167,12 @@
 
     /** Builds a VcnGatewayConnectionConfig with the specified parameters. */
     private VcnGatewayConnectionConfig(
+            @NonNull VcnControlPlaneConfig ctrlPlaneConfig,
             @NonNull Set<Integer> exposedCapabilities,
             @NonNull Set<Integer> underlyingCapabilities,
             @NonNull long[] retryIntervalsMs,
             @IntRange(from = MIN_MTU_V6) int maxMtu) {
+        mCtrlPlaneConfig = ctrlPlaneConfig;
         mExposedCapabilities = new TreeSet(exposedCapabilities);
         mUnderlyingCapabilities = new TreeSet(underlyingCapabilities);
         mRetryIntervalsMs = retryIntervalsMs;
@@ -184,11 +184,16 @@
     /** @hide */
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     public VcnGatewayConnectionConfig(@NonNull PersistableBundle in) {
+        final PersistableBundle ctrlPlaneConfigBundle =
+                in.getPersistableBundle(CTRL_PLANE_CONFIG_KEY);
+        Objects.requireNonNull(ctrlPlaneConfigBundle, "ctrlPlaneConfigBundle was null");
+
         final PersistableBundle exposedCapsBundle =
                 in.getPersistableBundle(EXPOSED_CAPABILITIES_KEY);
         final PersistableBundle underlyingCapsBundle =
                 in.getPersistableBundle(UNDERLYING_CAPABILITIES_KEY);
 
+        mCtrlPlaneConfig = VcnControlPlaneConfig.fromPersistableBundle(ctrlPlaneConfigBundle);
         mExposedCapabilities = new TreeSet<>(PersistableBundleUtils.toList(
                 exposedCapsBundle, PersistableBundleUtils.INTEGER_DESERIALIZER));
         mUnderlyingCapabilities = new TreeSet<>(PersistableBundleUtils.toList(
@@ -200,6 +205,8 @@
     }
 
     private void validate() {
+        Objects.requireNonNull(mCtrlPlaneConfig, "control plane config was null");
+
         Preconditions.checkArgument(
                 mExposedCapabilities != null && !mExposedCapabilities.isEmpty(),
                 "exposedCapsBundle was null or empty");
@@ -243,14 +250,23 @@
     }
 
     /**
+     * Returns control plane configuration.
+     *
+     * @hide
+     */
+    @NonNull
+    public VcnControlPlaneConfig getControlPlaneConfig() {
+        return mCtrlPlaneConfig.copy();
+    }
+
+    /**
      * Returns all exposed capabilities.
      *
      * <p>The returned integer-value capabilities will not contain duplicates, and will be sorted in
      * ascending numerical order.
      *
      * @see Builder#addExposedCapability(int)
-     * @see Builder#clearExposedCapability(int)
-     * @hide
+     * @see Builder#removeExposedCapability(int)
      */
     @NonNull
     public int[] getExposedCapabilities() {
@@ -278,8 +294,7 @@
      * <p>The returned integer-value capabilities will be sorted in ascending numerical order.
      *
      * @see Builder#addRequiredUnderlyingCapability(int)
-     * @see Builder#clearRequiredUnderlyingCapability(int)
-     * @hide
+     * @see Builder#removeRequiredUnderlyingCapability(int)
      */
     @NonNull
     public int[] getRequiredUnderlyingCapabilities() {
@@ -305,7 +320,6 @@
      * Retrieves the configured retry intervals.
      *
      * @see Builder#setRetryInterval(long[])
-     * @hide
      */
     @NonNull
     public long[] getRetryInterval() {
@@ -317,7 +331,7 @@
      *
      * <p>Left to prevent the need to make major changes while changes are actively in flight.
      *
-     * @deprecated use getRequiredUnderlyingCapabilities() instead
+     * @deprecated use getRetryInterval() instead
      * @hide
      */
     @Deprecated
@@ -329,8 +343,7 @@
     /**
      * Retrieves the maximum MTU allowed for this Gateway Connection.
      *
-     * @see Builder.setMaxMtu(int)
-     * @hide
+     * @see Builder#setMaxMtu(int)
      */
     @IntRange(from = MIN_MTU_V6)
     public int getMaxMtu() {
@@ -347,6 +360,7 @@
     public PersistableBundle toPersistableBundle() {
         final PersistableBundle result = new PersistableBundle();
 
+        final PersistableBundle ctrlPlaneConfigBundle = mCtrlPlaneConfig.toPersistableBundle();
         final PersistableBundle exposedCapsBundle =
                 PersistableBundleUtils.fromList(
                         new ArrayList<>(mExposedCapabilities),
@@ -356,6 +370,7 @@
                         new ArrayList<>(mUnderlyingCapabilities),
                         PersistableBundleUtils.INTEGER_SERIALIZER);
 
+        result.putPersistableBundle(CTRL_PLANE_CONFIG_KEY, ctrlPlaneConfigBundle);
         result.putPersistableBundle(EXPOSED_CAPABILITIES_KEY, exposedCapsBundle);
         result.putPersistableBundle(UNDERLYING_CAPABILITIES_KEY, underlyingCapsBundle);
         result.putLongArray(RETRY_INTERVAL_MS_KEY, mRetryIntervalsMs);
@@ -388,10 +403,9 @@
 
     /**
      * This class is used to incrementally build {@link VcnGatewayConnectionConfig} objects.
-     *
-     * @hide
      */
     public static final class Builder {
+        @NonNull private final VcnControlPlaneConfig mCtrlPlaneConfig;
         @NonNull private final Set<Integer> mExposedCapabilities = new ArraySet();
         @NonNull private final Set<Integer> mUnderlyingCapabilities = new ArraySet();
         @NonNull private long[] mRetryIntervalsMs = DEFAULT_RETRY_INTERVALS_MS;
@@ -402,6 +416,26 @@
         //       when on Cell.
 
         /**
+         * Construct a Builder object.
+         *
+         * @param ctrlPlaneConfig the control plane configuration
+         * @see VcnControlPlaneConfig
+         * @hide
+         */
+        public Builder(@NonNull VcnControlPlaneConfig ctrlPlaneConfig) {
+            Objects.requireNonNull(ctrlPlaneConfig, "ctrlPlaneConfig was null");
+
+            mCtrlPlaneConfig = ctrlPlaneConfig;
+        }
+
+        /** Construct a Builder object. */
+        // TODO: Remove this constructor when #Builder(ctrlPlaneConfig) is exposed as public API.
+        // This constructor is created to avoid changing API shape in this CL
+        public Builder() {
+            mCtrlPlaneConfig = null;
+        }
+
+        /**
          * Add a capability that this VCN Gateway Connection will support.
          *
          * @param exposedCapability the app-facing capability to be exposed by this VCN Gateway
@@ -409,7 +443,6 @@
          * @return this {@link Builder} instance, for chaining
          * @see VcnGatewayConnectionConfig for a list of capabilities may be exposed by a Gateway
          *     Connection
-         * @hide
          */
         @NonNull
         public Builder addExposedCapability(@VcnSupportedCapability int exposedCapability) {
@@ -427,10 +460,10 @@
          * @return this {@link Builder} instance, for chaining
          * @see VcnGatewayConnectionConfig for a list of capabilities may be exposed by a Gateway
          *     Connection
-         * @hide
          */
         @NonNull
-        public Builder clearExposedCapability(@VcnSupportedCapability int exposedCapability) {
+        @SuppressLint("BuilderSetStyle") // For consistency with NetCaps.Builder add/removeCap
+        public Builder removeExposedCapability(@VcnSupportedCapability int exposedCapability) {
             checkValidCapability(exposedCapability);
 
             mExposedCapabilities.remove(exposedCapability);
@@ -445,7 +478,6 @@
          * @return this {@link Builder} instance, for chaining
          * @see VcnGatewayConnectionConfig for a list of capabilities may be required of underlying
          *     networks
-         * @hide
          */
         @NonNull
         public Builder addRequiredUnderlyingCapability(
@@ -468,10 +500,10 @@
          * @return this {@link Builder} instance, for chaining
          * @see VcnGatewayConnectionConfig for a list of capabilities may be required of underlying
          *     networks
-         * @hide
          */
         @NonNull
-        public Builder clearRequiredUnderlyingCapability(
+        @SuppressLint("BuilderSetStyle") // For consistency with NetCaps.Builder add/removeCap
+        public Builder removeRequiredUnderlyingCapability(
                 @VcnSupportedCapability int underlyingCapability) {
             checkValidCapability(underlyingCapability);
 
@@ -501,7 +533,6 @@
          *     15m]}
          * @return this {@link Builder} instance, for chaining
          * @see VcnManager for additional discussion on fail-safe mode
-         * @hide
          */
         @NonNull
         public Builder setRetryInterval(@NonNull long[] retryIntervalsMs) {
@@ -523,7 +554,6 @@
          * @param maxMtu the maximum MTU allowed for this Gateway Connection. Must be greater than
          *     the IPv6 minimum MTU of 1280. Defaults to 1500.
          * @return this {@link Builder} instance, for chaining
-         * @hide
          */
         @NonNull
         public Builder setMaxMtu(@IntRange(from = MIN_MTU_V6) int maxMtu) {
@@ -538,12 +568,15 @@
          * Builds and validates the VcnGatewayConnectionConfig.
          *
          * @return an immutable VcnGatewayConnectionConfig instance
-         * @hide
          */
         @NonNull
         public VcnGatewayConnectionConfig build() {
             return new VcnGatewayConnectionConfig(
-                    mExposedCapabilities, mUnderlyingCapabilities, mRetryIntervalsMs, mMaxMtu);
+                    mCtrlPlaneConfig,
+                    mExposedCapabilities,
+                    mUnderlyingCapabilities,
+                    mRetryIntervalsMs,
+                    mMaxMtu);
         }
     }
 }
diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java
index 1a38338..aea0ea9 100644
--- a/core/java/android/net/vcn/VcnManager.java
+++ b/core/java/android/net/vcn/VcnManager.java
@@ -17,12 +17,15 @@
 
 import static java.util.Objects.requireNonNull;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
 import android.content.Context;
 import android.net.LinkProperties;
 import android.net.NetworkCapabilities;
+import android.os.Binder;
 import android.os.ParcelUuid;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
@@ -31,6 +34,8 @@
 import com.android.internal.annotations.VisibleForTesting.Visibility;
 
 import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Collections;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
@@ -39,12 +44,12 @@
 /**
  * VcnManager publishes APIs for applications to configure and manage Virtual Carrier Networks.
  *
- * <p>A VCN creates a virtualization layer to allow MVNOs to aggregate heterogeneous physical
+ * <p>A VCN creates a virtualization layer to allow carriers to aggregate heterogeneous physical
  * networks, unifying them as a single carrier network. This enables infrastructure flexibility on
- * the part of MVNOs without impacting user connectivity, abstracting the physical network
+ * the part of carriers without impacting user connectivity, abstracting the physical network
  * technologies as an implementation detail of their public network.
  *
- * <p>Each VCN virtualizes an Carrier's network by building tunnels to a carrier's core network over
+ * <p>Each VCN virtualizes a carrier's network by building tunnels to a carrier's core network over
  * carrier-managed physical links and supports a IP mobility layer to ensure seamless transitions
  * between the underlying networks. Each VCN is configured based on a Subscription Group (see {@link
  * android.telephony.SubscriptionManager}) and aggregates all networks that are brought up based on
@@ -62,8 +67,6 @@
  * tasks. In Safe Mode, the system will allow underlying cellular networks to be used as default.
  * Additionally, during Safe Mode, the VCN will continue to retry the connections, and will
  * automatically exit Safe Mode if all active tunnels connect successfully.
- *
- * @hide
  */
 @SystemService(Context.VCN_MANAGEMENT_SERVICE)
 public class VcnManager {
@@ -101,7 +104,6 @@
         return Collections.unmodifiableMap(REGISTERED_POLICY_LISTENERS);
     }
 
-    // TODO: Make setVcnConfig(), clearVcnConfig() Public API
     /**
      * Sets the VCN configuration for a given subscription group.
      *
@@ -113,11 +115,10 @@
      *
      * @param subscriptionGroup the subscription group that the configuration should be applied to
      * @param config the configuration parameters for the VCN
-     * @throws SecurityException if the caller does not have carrier privileges, or is not running
-     *     as the primary user
-     * @throws IOException if the configuration failed to be persisted. A caller encountering this
-     *     exception should attempt to retry (possibly after a delay).
-     * @hide
+     * @throws SecurityException if the caller does not have carrier privileges for the provided
+     *     subscriptionGroup, or is not running as the primary user
+     * @throws IOException if the configuration failed to be saved and persisted to disk. This may
+     *     occur due to temporary disk errors, or more permanent conditions such as a full disk.
      */
     @RequiresPermission("carrier privileges") // TODO (b/72967236): Define a system-wide constant
     public void setVcnConfig(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config)
@@ -134,7 +135,6 @@
         }
     }
 
-    // TODO: Make setVcnConfig(), clearVcnConfig() Public API
     /**
      * Clears the VCN configuration for a given subscription group.
      *
@@ -145,9 +145,8 @@
      * @param subscriptionGroup the subscription group that the configuration should be applied to
      * @throws SecurityException if the caller does not have carrier privileges, or is not running
      *     as the primary user
-     * @throws IOException if the configuration failed to be cleared. A caller encountering this
-     *     exception should attempt to retry (possibly after a delay).
-     * @hide
+     * @throws IOException if the configuration failed to be cleared from disk. This may occur due
+     *     to temporary disk errors, or more permanent conditions such as a full disk.
      */
     @RequiresPermission("carrier privileges") // TODO (b/72967236): Define a system-wide constant
     public void clearVcnConfig(@NonNull ParcelUuid subscriptionGroup) throws IOException {
@@ -267,6 +266,154 @@
         }
     }
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+        VCN_ERROR_CODE_INTERNAL_ERROR,
+        VCN_ERROR_CODE_CONFIG_ERROR,
+        VCN_ERROR_CODE_NETWORK_ERROR
+    })
+    public @interface VcnErrorCode {}
+
+    /**
+     * Value indicating that an internal failure occurred in this Gateway Connection.
+     *
+     * @hide
+     */
+    public static final int VCN_ERROR_CODE_INTERNAL_ERROR = 0;
+
+    /**
+     * Value indicating that an error with this Gateway Connection's configuration occurred.
+     *
+     * <p>For example, this error code will be returned after authentication failures.
+     *
+     * @hide
+     */
+    public static final int VCN_ERROR_CODE_CONFIG_ERROR = 1;
+
+    /**
+     * Value indicating that a Network error occurred with this Gateway Connection.
+     *
+     * <p>For example, this error code will be returned if an underlying {@link android.net.Network}
+     * for this Gateway Connection is lost, or if an error occurs while resolving the connection
+     * endpoint address.
+     *
+     * @hide
+     */
+    public static final int VCN_ERROR_CODE_NETWORK_ERROR = 2;
+
+    // TODO: make VcnStatusCallback @SystemApi
+    /**
+     * VcnStatusCallback is the interface for Carrier apps to receive updates for their VCNs.
+     *
+     * <p>VcnStatusCallbacks may be registered before {@link VcnConfig}s are provided for a
+     * subscription group.
+     *
+     * @hide
+     */
+    public abstract static class VcnStatusCallback {
+        private VcnStatusCallbackBinder mCbBinder;
+
+        /**
+         * Invoked when the VCN for this Callback's subscription group enters safe mode.
+         *
+         * <p>A VCN will be put into safe mode if any of the gateway connections were unable to
+         * establish a connection within a system-determined timeout (while underlying networks were
+         * available).
+         *
+         * <p>A VCN-configuring app may opt to exit safe mode by (re)setting the VCN configuration
+         * via {@link #setVcnConfig(ParcelUuid, VcnConfig)}.
+         */
+        public abstract void onEnteredSafeMode();
+
+        /**
+         * Invoked when a VCN Gateway Connection corresponding to this callback's subscription
+         * encounters an error.
+         *
+         * @param networkCapabilities an array of underlying NetworkCapabilities for the Gateway
+         *     Connection that encountered the error for identification purposes. These will be a
+         *     sorted list with no duplicates, matching one of the {@link
+         *     VcnGatewayConnectionConfig}s set in the {@link VcnConfig} for this subscription
+         *     group.
+         * @param errorCode {@link VcnErrorCode} to indicate the error that occurred
+         * @param detail Throwable to provide additional information about the error, or {@code
+         *     null} if none
+         */
+        public abstract void onGatewayConnectionError(
+                @NonNull int[] networkCapabilities,
+                @VcnErrorCode int errorCode,
+                @Nullable Throwable detail);
+    }
+
+    /**
+     * Registers the given callback to receive status updates for the specified subscription.
+     *
+     * <p>Callbacks can be registered for a subscription before {@link VcnConfig}s are set for it.
+     *
+     * <p>A {@link VcnStatusCallback} may only be registered for one subscription at a time. {@link
+     * VcnStatusCallback}s may be reused once unregistered.
+     *
+     * <p>A {@link VcnStatusCallback} will only be invoked if the registering package has carrier
+     * privileges for the specified subscription at the time of invocation.
+     *
+     * @param subscriptionGroup The subscription group to match for callbacks
+     * @param executor The {@link Executor} to be used for invoking callbacks
+     * @param callback The VcnStatusCallback to be registered
+     * @throws IllegalStateException if callback is currently registered with VcnManager
+     * @hide
+     */
+    public void registerVcnStatusCallback(
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull Executor executor,
+            @NonNull VcnStatusCallback callback) {
+        requireNonNull(subscriptionGroup, "subscriptionGroup must not be null");
+        requireNonNull(executor, "executor must not be null");
+        requireNonNull(callback, "callback must not be null");
+
+        synchronized (callback) {
+            if (callback.mCbBinder != null) {
+                throw new IllegalStateException("callback is already registered with VcnManager");
+            }
+            callback.mCbBinder = new VcnStatusCallbackBinder(executor, callback);
+
+            try {
+                mService.registerVcnStatusCallback(
+                        subscriptionGroup, callback.mCbBinder, mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                callback.mCbBinder = null;
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Unregisters the given callback.
+     *
+     * <p>Once unregistered, the callback will stop receiving status updates for the subscription it
+     * was registered with.
+     *
+     * @param callback The callback to be unregistered
+     * @hide
+     */
+    public void unregisterVcnStatusCallback(@NonNull VcnStatusCallback callback) {
+        requireNonNull(callback, "callback must not be null");
+
+        synchronized (callback) {
+            if (callback.mCbBinder == null) {
+                // no Binder attached to this callback, so it's not currently registered
+                return;
+            }
+
+            try {
+                mService.unregisterVcnStatusCallback(callback.mCbBinder);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            } finally {
+                callback.mCbBinder = null;
+            }
+        }
+    }
+
     /**
      * Binder wrapper for added VcnUnderlyingNetworkPolicyListeners to receive signals from System
      * Server.
@@ -286,7 +433,62 @@
 
         @Override
         public void onPolicyChanged() {
-            mExecutor.execute(() -> mListener.onPolicyChanged());
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(() -> mListener.onPolicyChanged()));
+        }
+    }
+
+    /**
+     * Binder wrapper for VcnStatusCallbacks to receive signals from VcnManagementService.
+     *
+     * @hide
+     */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static class VcnStatusCallbackBinder extends IVcnStatusCallback.Stub {
+        @NonNull private final Executor mExecutor;
+        @NonNull private final VcnStatusCallback mCallback;
+
+        public VcnStatusCallbackBinder(
+                @NonNull Executor executor, @NonNull VcnStatusCallback callback) {
+            mExecutor = executor;
+            mCallback = callback;
+        }
+
+        @Override
+        public void onEnteredSafeMode() {
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(() -> mCallback.onEnteredSafeMode()));
+        }
+
+        // TODO(b/180521637): use ServiceSpecificException for safer Exception 'parceling'
+        @Override
+        public void onGatewayConnectionError(
+                @NonNull int[] networkCapabilities,
+                @VcnErrorCode int errorCode,
+                @Nullable String exceptionClass,
+                @Nullable String exceptionMessage) {
+            final Throwable cause = createThrowableByClassName(exceptionClass, exceptionMessage);
+
+            Binder.withCleanCallingIdentity(
+                    () ->
+                            mExecutor.execute(
+                                    () ->
+                                            mCallback.onGatewayConnectionError(
+                                                    networkCapabilities, errorCode, cause)));
+        }
+
+        private static Throwable createThrowableByClassName(
+                @Nullable String className, @Nullable String message) {
+            if (className == null) {
+                return null;
+            }
+
+            try {
+                Class<?> c = Class.forName(className);
+                return (Throwable) c.getConstructor(String.class).newInstance(message);
+            } catch (ReflectiveOperationException | ClassCastException e) {
+                return new RuntimeException(className + ": " + message);
+            }
         }
     }
 }
diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkSpecifier.java b/core/java/android/net/vcn/VcnUnderlyingNetworkSpecifier.java
new file mode 100644
index 0000000..a975637
--- /dev/null
+++ b/core/java/android/net/vcn/VcnUnderlyingNetworkSpecifier.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vcn;
+
+import android.annotation.NonNull;
+import android.net.NetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.internal.util.ArrayUtils;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * NetworkSpecifier object for VCN underlying network requests.
+ *
+ * <p>This matches any underlying network with the appropriate subIds.
+ *
+ * @hide
+ */
+public final class VcnUnderlyingNetworkSpecifier extends NetworkSpecifier implements Parcelable {
+    @NonNull private final int[] mSubIds;
+
+    /**
+     * Builds a new VcnUnderlyingNetworkSpecifier with the given list of subIds
+     *
+     * @hide
+     */
+    public VcnUnderlyingNetworkSpecifier(@NonNull int[] subIds) {
+        mSubIds = Objects.requireNonNull(subIds, "subIds were null");
+    }
+
+    /**
+     * Retrieves the list of subIds supported by this VcnUnderlyingNetworkSpecifier
+     *
+     * @hide
+     */
+    @NonNull
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public int[] getSubIds() {
+        return mSubIds;
+    }
+
+    public static final @NonNull Creator<VcnUnderlyingNetworkSpecifier> CREATOR =
+            new Creator<VcnUnderlyingNetworkSpecifier>() {
+                @Override
+                public VcnUnderlyingNetworkSpecifier createFromParcel(Parcel in) {
+                    int[] subIds = in.createIntArray();
+                    return new VcnUnderlyingNetworkSpecifier(subIds);
+                }
+
+                @Override
+                public VcnUnderlyingNetworkSpecifier[] newArray(int size) {
+                    return new VcnUnderlyingNetworkSpecifier[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeIntArray(mSubIds);
+    }
+
+    @Override
+    public int hashCode() {
+        return Arrays.hashCode(mSubIds);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof VcnUnderlyingNetworkSpecifier)) {
+            return false;
+        }
+
+        VcnUnderlyingNetworkSpecifier lhs = (VcnUnderlyingNetworkSpecifier) obj;
+        return Arrays.equals(mSubIds, lhs.mSubIds);
+    }
+
+    @Override
+    public String toString() {
+        return new StringBuilder()
+                .append("VcnUnderlyingNetworkSpecifier [")
+                .append("mSubIds = ").append(Arrays.toString(mSubIds))
+                .append("]")
+                .toString();
+    }
+
+    /** @hide */
+    @Override
+    public boolean canBeSatisfiedBy(NetworkSpecifier other) {
+        if (other instanceof TelephonyNetworkSpecifier) {
+            return ArrayUtils.contains(
+                    mSubIds, ((TelephonyNetworkSpecifier) other).getSubscriptionId());
+        }
+        // TODO(b/180140053): Allow matching against WifiNetworkAgentSpecifier
+
+        // MatchAllNetworkSpecifier matched in NetworkCapabilities.
+        return equals(other);
+    }
+}
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index b39c182..7437e037 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -113,6 +113,7 @@
     boolean hasBadge(int userId);
     boolean isUserUnlocked(int userId);
     boolean isUserRunning(int userId);
+    boolean isUserForeground();
     boolean isUserNameSet(int userId);
     boolean hasRestrictedProfiles();
     boolean requestQuietModeEnabled(String callingPackage, boolean enableQuietMode, int userId, in IntentSender target, int flags);
diff --git a/core/java/android/os/UidBatteryConsumer.java b/core/java/android/os/UidBatteryConsumer.java
index a828077..bb40d90 100644
--- a/core/java/android/os/UidBatteryConsumer.java
+++ b/core/java/android/os/UidBatteryConsumer.java
@@ -29,21 +29,11 @@
     private final int mUid;
     @Nullable
     private final String mPackageWithHighestDrain;
-    private boolean mSystemComponent;
 
     public int getUid() {
         return mUid;
     }
 
-    /**
-     * Returns true if this battery consumer is considered to be a part of the operating
-     * system itself. For example, the UidBatteryConsumer with the UID {@link Process#BLUETOOTH_UID}
-     * is a system component.
-     */
-    public boolean isSystemComponent() {
-        return mSystemComponent;
-    }
-
     @Nullable
     public String getPackageWithHighestDrain() {
         return mPackageWithHighestDrain;
@@ -52,7 +42,6 @@
     private UidBatteryConsumer(@NonNull Builder builder) {
         super(builder.mPowerComponentsBuilder.build());
         mUid = builder.mUid;
-        mSystemComponent = builder.mSystemComponent;
         mPackageWithHighestDrain = builder.mPackageWithHighestDrain;
     }
 
@@ -95,7 +84,6 @@
         private final BatteryStats.Uid mBatteryStatsUid;
         private final int mUid;
         private String mPackageWithHighestDrain;
-        private boolean mSystemComponent;
         private boolean mExcludeFromBatteryUsageStats;
 
         public Builder(int customPowerComponentCount, int customTimeComponentCount,
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index ea1ce37..8bdfd3d 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2300,6 +2300,19 @@
     }
 
     /**
+     * Checks if the calling user is running on foreground.
+     *
+     * @return whether the calling user is running on foreground.
+     */
+    public boolean isUserForeground() {
+        try {
+            return mService.isUserForeground();
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Return whether the calling user is running in an "unlocked" state.
      * <p>
      * On devices with direct boot, a user is unlocked only after they've
diff --git a/core/java/android/os/incremental/IncrementalFileStorages.java b/core/java/android/os/incremental/IncrementalFileStorages.java
index f2fe719..a078e04 100644
--- a/core/java/android/os/incremental/IncrementalFileStorages.java
+++ b/core/java/android/os/incremental/IncrementalFileStorages.java
@@ -36,6 +36,7 @@
 import android.content.Context;
 import android.content.pm.DataLoaderParams;
 import android.content.pm.IDataLoaderStatusListener;
+import android.content.pm.IPackageLoadingProgressCallback;
 import android.content.pm.InstallationFileParcel;
 
 import java.io.File;
@@ -71,7 +72,8 @@
             @Nullable StorageHealthCheckParams healthCheckParams,
             @Nullable IStorageHealthListener healthListener,
             @NonNull List<InstallationFileParcel> addedFiles,
-            @NonNull PerUidReadTimeouts[] perUidReadTimeouts) throws IOException {
+            @NonNull PerUidReadTimeouts[] perUidReadTimeouts,
+            IPackageLoadingProgressCallback progressCallback) throws IOException {
         // TODO(b/136132412): validity check if session should not be incremental
         IncrementalManager incrementalManager = (IncrementalManager) context.getSystemService(
                 Context.INCREMENTAL_SERVICE);
@@ -95,6 +97,11 @@
                 throw new IOException("Unknown file location: " + file.location);
             }
         }
+        // Register progress loading callback after files have been added
+        if (progressCallback != null) {
+            incrementalManager.registerLoadingProgressCallback(stageDir.getAbsolutePath(),
+                    progressCallback);
+        }
         result.startLoading(dataLoaderParams, statusListener, healthCheckParams, healthListener,
                 perUidReadTimeouts);
 
@@ -205,6 +212,7 @@
 
         try {
             mDefaultStorage.unBind(mStageDir.getAbsolutePath());
+            mDefaultStorage.unregisterLoadingProgressListener();
         } catch (IOException ignored) {
         }
         mDefaultStorage = null;
diff --git a/core/java/android/permission/AdminPermissionControlParams.aidl b/core/java/android/permission/AdminPermissionControlParams.aidl
new file mode 100644
index 0000000..35e63d4
--- /dev/null
+++ b/core/java/android/permission/AdminPermissionControlParams.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.permission;
+
+parcelable AdminPermissionControlParams;
diff --git a/core/java/android/permission/AdminPermissionControlParams.java b/core/java/android/permission/AdminPermissionControlParams.java
new file mode 100644
index 0000000..49507220
--- /dev/null
+++ b/core/java/android/permission/AdminPermissionControlParams.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.permission;
+
+import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT;
+import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED;
+import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED;
+
+import static com.android.internal.util.Preconditions.checkArgument;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.app.admin.DevicePolicyManager;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * A data object representing an admin's request to control a certain permission
+ * for a certain app.
+ * This class is processed by the Permission Controller's
+ * setRuntimePermissionGrantStateByDeviceAdmin method.
+ *
+ * @hide
+ */
+@SystemApi
+public final class AdminPermissionControlParams implements Parcelable {
+    // The package to grant/deny the permission to.
+    private final @NonNull String mGranteePackageName;
+    // The permission to grant/deny.
+    private final @NonNull String mPermission;
+    // The grant state (granted/denied/default).
+    private final @DevicePolicyManager.PermissionGrantState int mGrantState;
+    // Whether the admin can grant sensors-related permissions.
+    private final boolean mCanAdminGrantSensorsPermissions;
+
+    /**
+     * @hide
+     * A new instance is only created by the framework, so the constructor need not be visible
+     * as system API.
+     */
+    public AdminPermissionControlParams(@NonNull String granteePackageName,
+            @NonNull String permission,
+            int grantState, boolean canAdminGrantSensorsPermissions) {
+        Preconditions.checkStringNotEmpty(granteePackageName, "Package name must not be empty.");
+        Preconditions.checkStringNotEmpty(permission, "Permission must not be empty.");
+        checkArgument(grantState == PERMISSION_GRANT_STATE_GRANTED
+                || grantState == PERMISSION_GRANT_STATE_DENIED
+                || grantState == PERMISSION_GRANT_STATE_DEFAULT);
+
+        mGranteePackageName = granteePackageName;
+        mPermission = permission;
+        mGrantState = grantState;
+        mCanAdminGrantSensorsPermissions = canAdminGrantSensorsPermissions;
+    }
+
+    public static final @NonNull Creator<AdminPermissionControlParams> CREATOR =
+            new Creator<AdminPermissionControlParams>() {
+                @Override
+                public AdminPermissionControlParams createFromParcel(Parcel in) {
+                    String granteePackageName = in.readString();
+                    String permission = in.readString();
+                    int grantState = in.readInt();
+                    boolean mayAdminGrantSensorPermissions = in.readBoolean();
+
+                    return new AdminPermissionControlParams(granteePackageName, permission,
+                            grantState, mayAdminGrantSensorPermissions);
+                }
+
+                @Override
+                public AdminPermissionControlParams[] newArray(int size) {
+                    return new AdminPermissionControlParams[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(mGranteePackageName);
+        dest.writeString(mPermission);
+        dest.writeInt(mGrantState);
+        dest.writeBoolean(mCanAdminGrantSensorsPermissions);
+    }
+
+    /** Returns the name of the package the permission applies to */
+    public @NonNull String getGranteePackageName() {
+        return mGranteePackageName;
+    }
+
+    /** Returns the permission name */
+    public @NonNull String getPermission() {
+        return mPermission;
+    }
+
+    /** Returns the grant state */
+    public int getGrantState() {
+        return mGrantState;
+    }
+
+    /**
+     * return true if the admin may control grants of permissions related to sensors.
+     */
+    public boolean canAdminGrantSensorsPermissions() {
+        return mCanAdminGrantSensorsPermissions;
+    }
+
+    @Override
+    public String toString() {
+        return String.format(
+                "Grantee %s Permission %s state: %d admin grant of sensors permissions: %b",
+                mGranteePackageName, mPermission, mGrantState, mCanAdminGrantSensorsPermissions);
+    }
+}
diff --git a/core/java/android/permission/IPermissionController.aidl b/core/java/android/permission/IPermissionController.aidl
index 084cc2f..6d677f3 100644
--- a/core/java/android/permission/IPermissionController.aidl
+++ b/core/java/android/permission/IPermissionController.aidl
@@ -20,6 +20,7 @@
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.os.UserHandle;
+import android.permission.AdminPermissionControlParams;
 import com.android.internal.infra.AndroidFuture;
 
 /**
@@ -39,8 +40,8 @@
     void countPermissionApps(in List<String> permissionNames, int flags,
             in AndroidFuture callback);
     void getPermissionUsages(boolean countSystem, long numMillis, in AndroidFuture callback);
-    void setRuntimePermissionGrantStateByDeviceAdmin(String callerPackageName, String packageName,
-                String permission, int grantState, in AndroidFuture callback);
+    void setRuntimePermissionGrantStateByDeviceAdminFromParams(String callerPackageName,
+            in AdminPermissionControlParams params, in AndroidFuture callback);
     void grantOrUpgradeDefaultRuntimePermissions(in AndroidFuture callback);
     void notifyOneTimePermissionSessionTimeout(String packageName);
     void updateUserSensitiveForApp(int uid, in AndroidFuture callback);
diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java
index f306805..084b18e 100644
--- a/core/java/android/permission/PermissionControllerManager.java
+++ b/core/java/android/permission/PermissionControllerManager.java
@@ -16,13 +16,9 @@
 
 package android.permission;
 
-import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT;
-import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED;
-import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED;
 import static android.permission.PermissionControllerService.SERVICE_INTERFACE;
 
 import static com.android.internal.util.FunctionalUtils.uncheckExceptions;
-import static com.android.internal.util.Preconditions.checkArgument;
 import static com.android.internal.util.Preconditions.checkArgumentNonnegative;
 import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull;
 import static com.android.internal.util.Preconditions.checkFlagsArgument;
@@ -39,7 +35,6 @@
 import android.annotation.SystemService;
 import android.annotation.TestApi;
 import android.app.ActivityThread;
-import android.app.admin.DevicePolicyManager.PermissionGrantState;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -70,6 +65,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
@@ -323,11 +319,11 @@
 
     /**
      * Set the runtime permission state from a device admin.
+     * This variant takes into account whether the admin may or may not grant sensors-related
+     * permissions.
      *
      * @param callerPackageName The package name of the admin requesting the change
-     * @param packageName Package the permission belongs to
-     * @param permission Permission to change
-     * @param grantState State to set the permission into
+     * @param params Information about the permission being granted.
      * @param executor Executor to run the {@code callback} on
      * @param callback The callback
      *
@@ -338,30 +334,27 @@
             Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY},
             conditional = true)
     public void setRuntimePermissionGrantStateByDeviceAdmin(@NonNull String callerPackageName,
-            @NonNull String packageName, @NonNull String permission,
-            @PermissionGrantState int grantState, @NonNull @CallbackExecutor Executor executor,
+            @NonNull AdminPermissionControlParams params,
+            @NonNull @CallbackExecutor Executor executor,
             @NonNull Consumer<Boolean> callback) {
         checkStringNotEmpty(callerPackageName);
-        checkStringNotEmpty(packageName);
-        checkStringNotEmpty(permission);
-        checkArgument(grantState == PERMISSION_GRANT_STATE_GRANTED
-                || grantState == PERMISSION_GRANT_STATE_DENIED
-                || grantState == PERMISSION_GRANT_STATE_DEFAULT);
-        checkNotNull(executor);
-        checkNotNull(callback);
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+        Objects.requireNonNull(params, "Admin control params must not be null.");
 
         mRemoteService.postAsync(service -> {
             AndroidFuture<Boolean> setRuntimePermissionGrantStateResult = new AndroidFuture<>();
-            service.setRuntimePermissionGrantStateByDeviceAdmin(
-                    callerPackageName, packageName, permission, grantState,
+            service.setRuntimePermissionGrantStateByDeviceAdminFromParams(
+                    callerPackageName, params,
                     setRuntimePermissionGrantStateResult);
             return setRuntimePermissionGrantStateResult;
         }).whenCompleteAsync((setRuntimePermissionGrantStateResult, err) -> {
             final long token = Binder.clearCallingIdentity();
             try {
                 if (err != null) {
-                    Log.e(TAG, "Error setting permissions state for device admin " + packageName,
-                            err);
+                    Log.e(TAG,
+                            "Error setting permissions state for device admin "
+                                    + callerPackageName, err);
                     callback.accept(false);
                 } else {
                     callback.accept(Boolean.TRUE.equals(setRuntimePermissionGrantStateResult));
diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java
index 8105b65..ad9e8b3 100644
--- a/core/java/android/permission/PermissionControllerService.java
+++ b/core/java/android/permission/PermissionControllerService.java
@@ -16,9 +16,7 @@
 
 package android.permission;
 
-import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT;
 import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED;
-import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED;
 import static android.permission.PermissionControllerManager.COUNT_ONLY_WHEN_GRANTED;
 import static android.permission.PermissionControllerManager.COUNT_WHEN_SYSTEM;
 
@@ -259,6 +257,8 @@
     }
 
     /**
+     * @deprecated See {@link #onSetRuntimePermissionGrantStateByDeviceAdmin(String,
+     * AdminPermissionControlParams, Consumer)}.
      * Set the runtime permission state from a device admin.
      *
      * @param callerPackageName The package name of the admin requesting the change
@@ -267,6 +267,7 @@
      * @param grantState State to set the permission into
      * @param callback Callback waiting for whether the state could be set or not
      */
+    @Deprecated
     @BinderThread
     public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(
             @NonNull String callerPackageName, @NonNull String packageName,
@@ -274,6 +275,20 @@
             @NonNull Consumer<Boolean> callback);
 
     /**
+     * Set the runtime permission state from a device admin.
+     *
+     * @param callerPackageName The package name of the admin requesting the change
+     * @param params Parameters of admin request.
+     * @param callback Callback waiting for whether the state could be set or not
+     */
+    @BinderThread
+    public void onSetRuntimePermissionGrantStateByDeviceAdmin(
+            @NonNull String callerPackageName, @NonNull AdminPermissionControlParams params,
+            @NonNull Consumer<Boolean> callback) {
+        throw new AbstractMethodError("Must be overridden in implementing class");
+    }
+
+    /**
      * Called when a package is considered inactive based on the criteria given by
      * {@link PermissionManager#startOneTimePermissionSession(String, long, int, int)}.
      * This method is called at the end of a one-time permission session
@@ -468,32 +483,26 @@
             }
 
             @Override
-            public void setRuntimePermissionGrantStateByDeviceAdmin(String callerPackageName,
-                    String packageName, String permission, int grantState,
+            public void setRuntimePermissionGrantStateByDeviceAdminFromParams(
+                    String callerPackageName, AdminPermissionControlParams params,
                     AndroidFuture callback) {
                 checkStringNotEmpty(callerPackageName);
-                checkStringNotEmpty(packageName);
-                checkStringNotEmpty(permission);
-                checkArgument(grantState == PERMISSION_GRANT_STATE_GRANTED
-                        || grantState == PERMISSION_GRANT_STATE_DENIED
-                        || grantState == PERMISSION_GRANT_STATE_DEFAULT);
-                checkNotNull(callback);
-
-                if (grantState == PERMISSION_GRANT_STATE_DENIED) {
+                if (params.getGrantState() == PERMISSION_GRANT_STATE_DENIED) {
                     enforceSomePermissionsGrantedToCaller(
                             Manifest.permission.GRANT_RUNTIME_PERMISSIONS);
                 }
 
-                if (grantState == PERMISSION_GRANT_STATE_DENIED) {
+                if (params.getGrantState() == PERMISSION_GRANT_STATE_DENIED) {
                     enforceSomePermissionsGrantedToCaller(
                             Manifest.permission.REVOKE_RUNTIME_PERMISSIONS);
                 }
 
                 enforceSomePermissionsGrantedToCaller(
                         Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY);
+                checkNotNull(callback);
 
                 onSetRuntimePermissionGrantStateByDeviceAdmin(callerPackageName,
-                        packageName, permission, grantState, callback::complete);
+                        params, callback::complete);
             }
 
             @Override
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 91e091c..e134c29 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -525,6 +525,13 @@
      */
     public static final String NAMESPACE_INTERACTION_JANK_MONITOR = "interaction_jank_monitor";
 
+    /**
+     * Namespace for game overlay related features.
+     *
+     * @hide
+     */
+    public static final String NAMESPACE_GAME_OVERLAY = "game_overlay";
+
     private static final Object sLock = new Object();
     @GuardedBy("sLock")
     private static ArrayMap<OnPropertiesChangedListener, Pair<String, Executor>> sListeners =
diff --git a/core/java/android/provider/SimPhonebookContract.java b/core/java/android/provider/SimPhonebookContract.java
index f3a7856..074d5f1 100644
--- a/core/java/android/provider/SimPhonebookContract.java
+++ b/core/java/android/provider/SimPhonebookContract.java
@@ -262,7 +262,7 @@
         @WorkerThread
         public static int getEncodedNameLength(
                 @NonNull ContentResolver resolver, @NonNull String name) {
-            name = Objects.requireNonNull(name);
+            Objects.requireNonNull(name);
             Bundle result = resolver.call(AUTHORITY, GET_ENCODED_NAME_LENGTH_METHOD_NAME, name,
                     null);
             if (result == null || !result.containsKey(EXTRA_ENCODED_NAME_LENGTH)) {
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index f013976e..7996f09 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -5286,7 +5286,8 @@
          * which network types are allowed for
          * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_USER},
          * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_POWER},
-         * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_CARRIER}.
+         * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_CARRIER},
+         * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G}.
          * <P>Type: TEXT </P>
          *
          * @hide
diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java
index a79b197..5a89cdf 100644
--- a/core/java/android/security/keymaster/KeymasterDefs.java
+++ b/core/java/android/security/keymaster/KeymasterDefs.java
@@ -188,6 +188,7 @@
     public static final int KM_PURPOSE_VERIFY = KeyPurpose.VERIFY;
     public static final int KM_PURPOSE_WRAP = KeyPurpose.WRAP_KEY;
     public static final int KM_PURPOSE_AGREE_KEY = KeyPurpose.AGREE_KEY;
+    public static final int KM_PURPOSE_ATTEST_KEY = KeyPurpose.ATTEST_KEY;
 
     // Key formats.
     public static final int KM_KEY_FORMAT_X509 = KeyFormat.X509;
diff --git a/core/java/android/speech/IRecognitionService.aidl b/core/java/android/speech/IRecognitionService.aidl
index f91e122..cc1cded 100644
--- a/core/java/android/speech/IRecognitionService.aidl
+++ b/core/java/android/speech/IRecognitionService.aidl
@@ -43,7 +43,7 @@
      * @param featureId The feature in the package
      */
     void startListening(in Intent recognizerIntent, in IRecognitionListener listener,
-            String packageName, String featureId);
+            String packageName, String featureId, int callingUid);
 
     /**
      * Stops listening for speech. Speech captured so far will be recognized as
@@ -62,6 +62,7 @@
      * @param listener to receive callbacks, note that this must be non-null
      * @param packageName the package name calling this API
      * @param featureId The feature in the package
+     * @param isShutdown Whether the cancellation is caused by a client calling #shutdown
      */
-    void cancel(in IRecognitionListener listener, String packageName, String featureId);
+    void cancel(in IRecognitionListener listener, String packageName, String featureId, boolean isShutdown);
 }
diff --git a/core/java/android/speech/IRecognitionServiceManager.aidl b/core/java/android/speech/IRecognitionServiceManager.aidl
index 7158ba2..8e5292d 100644
--- a/core/java/android/speech/IRecognitionServiceManager.aidl
+++ b/core/java/android/speech/IRecognitionServiceManager.aidl
@@ -16,6 +16,8 @@
 
 package android.speech;
 
+import android.content.ComponentName;
+
 import android.speech.IRecognitionServiceManagerCallback;
 
 /**
@@ -23,6 +25,10 @@
  *
  * {@hide}
  */
-interface IRecognitionServiceManager {
-    void createSession(in IRecognitionServiceManagerCallback callback);
+oneway interface IRecognitionServiceManager {
+    void createSession(
+        in ComponentName componentName,
+        in IBinder clientToken,
+        boolean onDevice,
+        in IRecognitionServiceManagerCallback callback);
 }
diff --git a/core/java/android/speech/IRecognitionServiceManagerCallback.aidl b/core/java/android/speech/IRecognitionServiceManagerCallback.aidl
index d760810..26afdaa 100644
--- a/core/java/android/speech/IRecognitionServiceManagerCallback.aidl
+++ b/core/java/android/speech/IRecognitionServiceManagerCallback.aidl
@@ -25,5 +25,5 @@
  */
 oneway interface IRecognitionServiceManagerCallback {
     void onSuccess(in IRecognitionService service);
-    void onError();
+    void onError(int errorCode);
 }
diff --git a/core/java/android/speech/RecognitionService.java b/core/java/android/speech/RecognitionService.java
index c97dbfe..fd584f1 100644
--- a/core/java/android/speech/RecognitionService.java
+++ b/core/java/android/speech/RecognitionService.java
@@ -105,17 +105,6 @@
             int callingUid) {
         if (mCurrentCallback == null) {
             if (DBG) Log.d(TAG, "created new mCurrentCallback, listener = " + listener.asBinder());
-            try {
-                listener.asBinder().linkToDeath(new IBinder.DeathRecipient() {
-                    @Override
-                    public void binderDied() {
-                        mHandler.sendMessage(mHandler.obtainMessage(MSG_CANCEL, listener));
-                    }
-                }, 0);
-            } catch (RemoteException re) {
-                Log.e(TAG, "dead listener on startListening");
-                return;
-            }
             mCurrentCallback = new Callback(listener, callingUid);
             RecognitionService.this.onStartListening(intent, mCurrentCallback);
         } else {
@@ -352,7 +341,6 @@
          * Return the Linux uid assigned to the process that sent you the current transaction that
          * is being processed. This is obtained from {@link Binder#getCallingUid()}.
          */
-        // TODO(b/176578753): need to make sure this is fixed when proxied through system.
         public int getCallingUid() {
             return mCallingUid;
         }
@@ -368,7 +356,7 @@
 
         @Override
         public void startListening(Intent recognizerIntent, IRecognitionListener listener,
-                String packageName, String featureId) {
+                String packageName, String featureId, int callingUid) {
             Preconditions.checkNotNull(packageName);
 
             if (DBG) Log.d(TAG, "startListening called by:" + listener.asBinder());
@@ -377,7 +365,7 @@
                     packageName, featureId)) {
                 service.mHandler.sendMessage(Message.obtain(service.mHandler,
                         MSG_START_LISTENING, service.new StartListeningArgs(
-                                recognizerIntent, listener, Binder.getCallingUid())));
+                                recognizerIntent, listener, callingUid)));
             }
         }
 
@@ -397,7 +385,7 @@
 
         @Override
         public void cancel(IRecognitionListener listener, String packageName,
-                String featureId) {
+                String featureId, boolean isShutdown) {
             Preconditions.checkNotNull(packageName);
 
             if (DBG) Log.d(TAG, "cancel called by:" + listener.asBinder());
diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java
index de879c6..850f997 100644
--- a/core/java/android/speech/SpeechRecognizer.java
+++ b/core/java/android/speech/SpeechRecognizer.java
@@ -20,8 +20,8 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.ServiceConnection;
 import android.content.pm.ResolveInfo;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -32,10 +32,9 @@
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.Slog;
 
-import java.util.LinkedList;
 import java.util.List;
-import java.util.Queue;
 
 /**
  * This class provides access to the speech recognition service. This service allows access to the
@@ -107,6 +106,12 @@
     /** Insufficient permissions */
     public static final int ERROR_INSUFFICIENT_PERMISSIONS = 9;
 
+    /** Too many requests from the same client. */
+    public static final int ERROR_TOO_MANY_REQUESTS = 10;
+
+    /** Server has been disconnected, e.g. because the app has crashed. */
+    public static final int ERROR_SERVER_DISCONNECTED = 11;
+
     /** action codes */
     private final static int MSG_START = 1;
     private final static int MSG_STOP = 2;
@@ -116,9 +121,6 @@
     /** The actual RecognitionService endpoint */
     private IRecognitionService mService;
 
-    /** The connection to the actual service */
-    private Connection mConnection;
-
     /** Context with which the manager was created */
     private final Context mContext;
     
@@ -151,15 +153,11 @@
         }
     };
 
-    /**
-     * Temporary queue, saving the messages until the connection will be established, afterwards,
-     * only mHandler will receive the messages
-     */
-    private final Queue<Message> mPendingTasks = new LinkedList<Message>();
-
     /** The Listener that will receive all the callbacks */
     private final InternalListener mListener = new InternalListener();
 
+    private final IBinder mClientToken = new Binder();
+
     /**
      * The right way to create a {@code SpeechRecognizer} is by using
      * {@link #createSpeechRecognizer} static factory method
@@ -181,30 +179,6 @@
     }
 
     /**
-     * Basic ServiceConnection that records the mService variable. Additionally, on creation it
-     * invokes the {@link IRecognitionService#startListening(Intent, IRecognitionListener)}.
-     */
-    private class Connection implements ServiceConnection {
-
-        public void onServiceConnected(final ComponentName name, final IBinder service) {
-            // always done on the application main thread, so no need to send message to mHandler
-            mService = IRecognitionService.Stub.asInterface(service);
-            if (DBG) Log.d(TAG, "onServiceConnected - Success");
-            while (!mPendingTasks.isEmpty()) {
-                mHandler.sendMessage(mPendingTasks.poll());
-            }
-        }
-
-        public void onServiceDisconnected(final ComponentName name) {
-            // always done on the application main thread, so no need to send message to mHandler
-            mService = null;
-            mConnection = null;
-            mPendingTasks.clear();
-            if (DBG) Log.d(TAG, "onServiceDisconnected - Success");
-        }
-    }
-
-    /**
      * Checks whether a speech recognition service is available on the system. If this method
      * returns {@code false}, {@link SpeechRecognizer#createSpeechRecognizer(Context)} will
      * fail.
@@ -303,87 +277,52 @@
             throw new IllegalArgumentException("intent must not be null");
         }
         checkIsCalledFromMainThread();
-        if (mConnection == null) { // first time connection
-            // TODO(b/176578753): both flows should go through system service.
-            if (mOnDevice) {
-                connectToSystemService();
-            } else {
-                connectToService();
+
+        if (DBG) {
+            Slog.i(TAG, "#startListening called");
+            if (mService == null) {
+                Slog.i(TAG, "Connection is not established yet");
             }
         }
-        putMessage(Message.obtain(mHandler, MSG_START, recognizerIntent));
-    }
 
-    private void connectToSystemService() {
-        mManagerService = IRecognitionServiceManager.Stub.asInterface(
-                ServiceManager.getService(Context.SPEECH_RECOGNITION_SERVICE));
-
-        if (mManagerService == null) {
-            mListener.onError(ERROR_CLIENT);
-            return;
-        }
-
-        try {
-            // TODO(b/176578753): this has to supply information on whether to use on-device impl.
-            mManagerService.createSession(new IRecognitionServiceManagerCallback.Stub(){
-                @Override
-                public void onSuccess(IRecognitionService service) throws RemoteException {
-                    mService = service;
-                }
-
-                @Override
-                public void onError() throws RemoteException {
-                    Log.e(TAG, "Bind to system recognition service failed");
-                    mListener.onError(ERROR_CLIENT);
-                }
-            });
-        } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
-        }
-    }
-
-    private void connectToService() {
-        mConnection = new Connection();
-
-        Intent serviceIntent = new Intent(RecognitionService.SERVICE_INTERFACE);
-
-        if (mServiceComponent == null) {
-            String serviceComponent = Settings.Secure.getString(mContext.getContentResolver(),
-                    Settings.Secure.VOICE_RECOGNITION_SERVICE);
-
-            if (TextUtils.isEmpty(serviceComponent)) {
-                Log.e(TAG, "no selected voice recognition service");
-                mListener.onError(ERROR_CLIENT);
-                return;
-            }
-
-            serviceIntent.setComponent(
-                    ComponentName.unflattenFromString(serviceComponent));
+        if (mService == null) {
+            // First time connection: first establish a connection, then dispatch #startListening.
+            connectToSystemService(
+                    () -> putMessage(Message.obtain(mHandler, MSG_START, recognizerIntent)));
         } else {
-            serviceIntent.setComponent(mServiceComponent);
-        }
-        if (!mContext.bindService(serviceIntent, mConnection,
-                Context.BIND_AUTO_CREATE | Context.BIND_INCLUDE_CAPABILITIES)) {
-            Log.e(TAG, "bind to recognition service failed");
-            mConnection = null;
-            mService = null;
-            mListener.onError(ERROR_CLIENT);
-            return;
+            putMessage(Message.obtain(mHandler, MSG_START, recognizerIntent));
         }
     }
 
     /**
      * Stops listening for speech. Speech captured so far will be recognized as if the user had
-     * stopped speaking at this point. Note that in the default case, this does not need to be
-     * called, as the speech endpointer will automatically stop the recognizer listening when it
-     * determines speech has completed. However, you can manipulate endpointer parameters directly
-     * using the intent extras defined in {@link RecognizerIntent}, in which case you may sometimes
-     * want to manually call this method to stop listening sooner. Please note that
+     * stopped speaking at this point.
+     *
+     * <p>Note that in the default case, this does not need to be called, as the speech endpointer
+     * will automatically stop the recognizer listening when it determines speech has completed.
+     * However, you can manipulate endpointer parameters directly using the intent extras defined in
+     * {@link RecognizerIntent}, in which case you may sometimes want to manually call this method
+     * to stop listening sooner.
+     *
+     * <p>Upon invocation clients must wait until {@link RecognitionListener#onResults} or
+     * {@link RecognitionListener#onError} are invoked before calling
+     * {@link SpeechRecognizer#startListening} again. Otherwise such an attempt would be rejected by
+     * recognition service.
+     *
+     * <p>Please note that
      * {@link #setRecognitionListener(RecognitionListener)} should be called beforehand, otherwise
      * no notifications will be received.
      */
     public void stopListening() {
         checkIsCalledFromMainThread();
+
+        if (DBG) {
+            Slog.i(TAG, "#stopListening called");
+            if (mService == null) {
+                Slog.i(TAG, "Connection is not established yet");
+            }
+        }
+
         putMessage(Message.obtain(mHandler, MSG_STOP));
     }
 
@@ -405,11 +344,7 @@
     }
 
     private void putMessage(Message msg) {
-        if (mService == null) {
-            mPendingTasks.offer(msg);
-        } else {
-            mHandler.sendMessage(msg);
-        }
+        mHandler.sendMessage(msg);
     }
 
     /** sends the actual message to the service */
@@ -419,7 +354,7 @@
         }
         try {
             mService.startListening(recognizerIntent, mListener, mContext.getOpPackageName(),
-                    mContext.getAttributionTag());
+                    mContext.getAttributionTag(), android.os.Process.myUid());
             if (DBG) Log.d(TAG, "service start listening command succeded");
         } catch (final RemoteException e) {
             Log.e(TAG, "startListening() failed", e);
@@ -448,7 +383,11 @@
             return;
         }
         try {
-            mService.cancel(mListener, mContext.getOpPackageName(), mContext.getAttributionTag());
+            mService.cancel(
+                    mListener,
+                    mContext.getOpPackageName(),
+                    mContext.getAttributionTag(),
+                    false /* isShutdown */);
             if (DBG) Log.d(TAG, "service cancel command succeded");
         } catch (final RemoteException e) {
             Log.e(TAG, "cancel() failed", e);
@@ -471,28 +410,94 @@
         mListener.mInternalListener = listener;
     }
 
-    /**
-     * Destroys the {@code SpeechRecognizer} object.
-     */
+    /** Destroys the {@code SpeechRecognizer} object. */
     public void destroy() {
         if (mService != null) {
             try {
                 mService.cancel(mListener, mContext.getOpPackageName(),
-                        mContext.getAttributionTag());
+                        mContext.getAttributionTag(), true /* isShutdown */);
             } catch (final RemoteException e) {
                 // Not important
             }
         }
 
-        if (mConnection != null) {
-            mContext.unbindService(mConnection);
-        }
-        mPendingTasks.clear();
         mService = null;
-        mConnection = null;
         mListener.mInternalListener = null;
     }
 
+    /** Establishes a connection to system server proxy and initializes the session. */
+    private void connectToSystemService(Runnable onSuccess) {
+        mManagerService = IRecognitionServiceManager.Stub.asInterface(
+                ServiceManager.getService(Context.SPEECH_RECOGNITION_SERVICE));
+
+        if (mManagerService == null) {
+            mListener.onError(ERROR_CLIENT);
+            return;
+        }
+
+        ComponentName componentName = getSpeechRecognizerComponentName();
+
+        if (!mOnDevice && componentName == null) {
+            mListener.onError(ERROR_CLIENT);
+            return;
+        }
+
+        try {
+            mManagerService.createSession(
+                    componentName,
+                    mClientToken,
+                    mOnDevice,
+                    new IRecognitionServiceManagerCallback.Stub(){
+                        @Override
+                        public void onSuccess(IRecognitionService service) throws RemoteException {
+                            mService = service;
+                            onSuccess.run();
+                        }
+
+                        @Override
+                        public void onError(int errorCode) throws RemoteException {
+                            Log.e(TAG, "Bind to system recognition service failed");
+                            mListener.onError(errorCode);
+                        }
+                    });
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the component name to be used for establishing a connection, based on the parameters
+     * used during initialization.
+     *
+     * <p>Note the 3 different scenarios:
+     * <ol>
+     *     <li>On-device speech recognizer which is determined by the manufacturer and not
+     *     changeable by the user
+     *     <li>Default user-selected speech recognizer as specified by
+     *     {@code Settings.Secure.VOICE_RECOGNITION_SERVICE}
+     *     <li>Custom speech recognizer supplied by the client.
+     */
+    private ComponentName getSpeechRecognizerComponentName() {
+        if (mOnDevice) {
+            return null;
+        }
+
+        if (mServiceComponent != null) {
+            return mServiceComponent;
+        }
+
+        String serviceComponent = Settings.Secure.getString(mContext.getContentResolver(),
+                Settings.Secure.VOICE_RECOGNITION_SERVICE);
+
+        if (TextUtils.isEmpty(serviceComponent)) {
+            Log.e(TAG, "no selected voice recognition service");
+            mListener.onError(ERROR_CLIENT);
+            return null;
+        }
+
+        return ComponentName.unflattenFromString(serviceComponent);
+    }
+
     /**
      * Internal wrapper of IRecognitionListener which will propagate the results to
      * RecognitionListener
diff --git a/core/java/android/speech/tts/ITextToSpeechManager.aidl b/core/java/android/speech/tts/ITextToSpeechManager.aidl
deleted file mode 100644
index e6b63df..0000000
--- a/core/java/android/speech/tts/ITextToSpeechManager.aidl
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.speech.tts;
-
-import android.speech.tts.ITextToSpeechSessionCallback;
-
-/**
- * TextToSpeechManagerService interface. Allows opening {@link TextToSpeech} session with the
- * specified provider proxied by the system service.
- *
- * @hide
- */
-oneway interface ITextToSpeechManager {
-    void createSession(in String engine, in ITextToSpeechSessionCallback managerCallback);
-}
diff --git a/core/java/android/speech/tts/ITextToSpeechSessionCallback.aidl b/core/java/android/speech/tts/ITextToSpeechSessionCallback.aidl
deleted file mode 100644
index 545622a..0000000
--- a/core/java/android/speech/tts/ITextToSpeechSessionCallback.aidl
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.speech.tts;
-import android.speech.tts.ITextToSpeechSession;
-
-/**
- * Callback interface for a session created by {@link ITextToSpeechManager} API.
- *
- * @hide
- */
-oneway interface ITextToSpeechSessionCallback {
-
-    void onConnected(in ITextToSpeechSession session, in IBinder serviceBinder);
-
-    void onDisconnected();
-
-    void onError(in String errorInfo);
-}
\ No newline at end of file
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index 5d66dc7..7a18538 100644
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -35,7 +35,6 @@
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -52,7 +51,6 @@
 import java.util.Map;
 import java.util.MissingResourceException;
 import java.util.Set;
-import java.util.concurrent.Executor;
 
 /**
  *
@@ -697,8 +695,6 @@
         public static final String KEY_FEATURE_NETWORK_RETRIES_COUNT = "networkRetriesCount";
     }
 
-    private static final boolean DEBUG = false;
-
     private final Context mContext;
     @UnsupportedAppUsage
     private Connection mConnectingServiceConnection;
@@ -720,9 +716,6 @@
     private final Map<CharSequence, Uri> mUtterances;
     private final Bundle mParams = new Bundle();
     private final TtsEngines mEnginesHelper;
-    private final boolean mIsSystem;
-    @Nullable private final Executor mInitExecutor;
-
     @UnsupportedAppUsage
     private volatile String mCurrentEngine = null;
 
@@ -765,21 +758,8 @@
      */
     public TextToSpeech(Context context, OnInitListener listener, String engine,
             String packageName, boolean useFallback) {
-        this(context, /* initExecutor= */ null, listener, engine, packageName,
-                useFallback, /* isSystem= */ true);
-    }
-
-    /**
-     * Used internally to instantiate TextToSpeech objects.
-     *
-     * @hide
-     */
-    private TextToSpeech(Context context, @Nullable Executor initExecutor,
-            OnInitListener initListener, String engine, String packageName, boolean useFallback,
-            boolean isSystem) {
         mContext = context;
-        mInitExecutor = initExecutor;
-        mInitListener = initListener;
+        mInitListener = listener;
         mRequestedEngine = engine;
         mUseFallback = useFallback;
 
@@ -788,9 +768,6 @@
         mUtteranceProgressListener = null;
 
         mEnginesHelper = new TtsEngines(mContext);
-
-        mIsSystem = isSystem;
-
         initTts();
     }
 
@@ -865,14 +842,10 @@
     }
 
     private boolean connectToEngine(String engine) {
-        Connection connection;
-        if (mIsSystem) {
-            connection = new SystemConnection();
-        } else {
-            connection = new DirectConnection();
-        }
-
-        boolean bound = connection.connect(engine);
+        Connection connection = new Connection();
+        Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE);
+        intent.setPackage(engine);
+        boolean bound = mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE);
         if (!bound) {
             Log.e(TAG, "Failed to bind to " + engine);
             return false;
@@ -884,19 +857,11 @@
     }
 
     private void dispatchOnInit(int result) {
-        Runnable onInitCommand = () -> {
-            synchronized (mStartLock) {
-                if (mInitListener != null) {
-                    mInitListener.onInit(result);
-                    mInitListener = null;
-                }
+        synchronized (mStartLock) {
+            if (mInitListener != null) {
+                mInitListener.onInit(result);
+                mInitListener = null;
             }
-        };
-
-        if (mInitExecutor != null) {
-            mInitExecutor.execute(onInitCommand);
-        } else {
-            onInitCommand.run();
         }
     }
 
@@ -2162,17 +2127,13 @@
         return mEnginesHelper.getEngines();
     }
 
-    private abstract class Connection implements ServiceConnection {
+    private class Connection implements ServiceConnection {
         private ITextToSpeechService mService;
 
         private SetupConnectionAsyncTask mOnSetupConnectionAsyncTask;
 
         private boolean mEstablished;
 
-        abstract boolean connect(String engine);
-
-        abstract void disconnect();
-
         private final ITextToSpeechCallback.Stub mCallback =
                 new ITextToSpeechCallback.Stub() {
                     public void onStop(String utteranceId, boolean isStarted)
@@ -2238,6 +2199,11 @@
                 };
 
         private class SetupConnectionAsyncTask extends AsyncTask<Void, Void, Integer> {
+            private final ComponentName mName;
+
+            public SetupConnectionAsyncTask(ComponentName name) {
+                mName = name;
+            }
 
             @Override
             protected Integer doInBackground(Void... params) {
@@ -2261,7 +2227,7 @@
                             mParams.putString(Engine.KEY_PARAM_VOICE_NAME, defaultVoiceName);
                         }
 
-                        Log.i(TAG, "Setting up the connection to TTS engine...");
+                        Log.i(TAG, "Set up connection to " + mName);
                         return SUCCESS;
                     } catch (RemoteException re) {
                         Log.e(TAG, "Error connecting to service, setCallback() failed");
@@ -2283,11 +2249,11 @@
         }
 
         @Override
-        public void onServiceConnected(ComponentName componentName, IBinder service) {
+        public void onServiceConnected(ComponentName name, IBinder service) {
             synchronized(mStartLock) {
                 mConnectingServiceConnection = null;
 
-                Log.i(TAG, "Connected to TTS engine");
+                Log.i(TAG, "Connected to " + name);
 
                 if (mOnSetupConnectionAsyncTask != null) {
                     mOnSetupConnectionAsyncTask.cancel(false);
@@ -2297,7 +2263,7 @@
                 mServiceConnection = Connection.this;
 
                 mEstablished = false;
-                mOnSetupConnectionAsyncTask = new SetupConnectionAsyncTask();
+                mOnSetupConnectionAsyncTask = new SetupConnectionAsyncTask(name);
                 mOnSetupConnectionAsyncTask.execute();
             }
         }
@@ -2311,7 +2277,7 @@
          *
          * @return true if we cancel mOnSetupConnectionAsyncTask in progress.
          */
-        protected boolean clearServiceConnection() {
+        private boolean clearServiceConnection() {
             synchronized(mStartLock) {
                 boolean result = false;
                 if (mOnSetupConnectionAsyncTask != null) {
@@ -2329,8 +2295,8 @@
         }
 
         @Override
-        public void onServiceDisconnected(ComponentName componentName) {
-            Log.i(TAG, "Disconnected from TTS engine");
+        public void onServiceDisconnected(ComponentName name) {
+            Log.i(TAG, "Asked to disconnect from " + name);
             if (clearServiceConnection()) {
                 /* We need to protect against a rare case where engine
                  * dies just after successful connection - and we process onServiceDisconnected
@@ -2342,6 +2308,11 @@
             }
         }
 
+        public void disconnect() {
+            mContext.unbindService(this);
+            clearServiceConnection();
+        }
+
         public boolean isEstablished() {
             return mService != null && mEstablished;
         }
@@ -2371,91 +2342,6 @@
         }
     }
 
-    // Currently all the clients are routed through the System connection. Direct connection
-    // is left for debugging, testing and benchmarking purposes.
-    // TODO(b/179599071): Remove direct connection once system one is fully tested.
-    private class DirectConnection extends Connection {
-        @Override
-        boolean connect(String engine) {
-            Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE);
-            intent.setPackage(engine);
-            return mContext.bindService(intent, this, Context.BIND_AUTO_CREATE);
-        }
-
-        @Override
-        void disconnect() {
-            mContext.unbindService(this);
-            clearServiceConnection();
-        }
-    }
-
-    private class SystemConnection extends Connection {
-
-        @Nullable
-        private volatile ITextToSpeechSession mSession;
-
-        @Override
-        boolean connect(String engine) {
-            IBinder binder = ServiceManager.getService(Context.TEXT_TO_SPEECH_MANAGER_SERVICE);
-
-            ITextToSpeechManager manager = ITextToSpeechManager.Stub.asInterface(binder);
-
-            if (manager == null) {
-                Log.e(TAG, "System service is not available!");
-                return false;
-            }
-
-            if (DEBUG) {
-                Log.d(TAG, "Connecting to engine: " + engine);
-            }
-
-            try {
-                manager.createSession(engine, new ITextToSpeechSessionCallback.Stub() {
-                    @Override
-                    public void onConnected(ITextToSpeechSession session, IBinder serviceBinder) {
-                        mSession = session;
-                        onServiceConnected(
-                                /* componentName= */ null,
-                                serviceBinder);
-                    }
-
-                    @Override
-                    public void onDisconnected() {
-                        onServiceDisconnected(/* componentName= */ null);
-                    }
-
-                    @Override
-                    public void onError(String errorInfo) {
-                        Log.w(TAG, "System TTS connection error: " + errorInfo);
-                        // The connection was not established successfully - handle as
-                        // disconnection: clear the state and notify the user.
-                        onServiceDisconnected(/* componentName= */ null);
-                    }
-                });
-
-                return true;
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Error communicating with the System Server: ", ex);
-                throw ex.rethrowFromSystemServer();
-            }
-        }
-
-        @Override
-        void disconnect() {
-            ITextToSpeechSession session = mSession;
-
-            if (session != null) {
-                try {
-                    session.disconnect();
-                } catch (RemoteException ex) {
-                    Log.w(TAG, "Error disconnecting session", ex);
-                }
-
-                clearServiceConnection();
-            }
-        }
-    }
-
     private interface Action<R> {
         R run(ITextToSpeechService service) throws RemoteException;
     }
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index 77f9c1a..e7ceada 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -1840,13 +1840,15 @@
          *
          * @param allowedNetworkTypesList Map associating all allowed network type reasons
          * ({@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_USER},
-         * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_POWER}, and
-         * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_CARRIER}) with reason's allowed
+         * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_POWER},
+         * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_CARRIER}, and
+         * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G}) with reason's allowed
          * network type values.
          * For example:
          * map{{TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_USER, long type value},
          *     {TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_POWER, long type value},
-         *     {TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_CARRIER, long type value}}
+         *     {TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_CARRIER, long type value},
+         *     {TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G, long type value}}
          */
         @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
         void onAllowedNetworkTypesChanged(
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 87820a8..e599888 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -12706,12 +12706,12 @@
         // When the battery is not on, we don't attribute the cpu times to any timers but we still
         // need to take the snapshots.
         if (!onBattery) {
-            mCpuUidUserSysTimeReader.readDelta(null);
-            mCpuUidFreqTimeReader.readDelta(null);
+            mCpuUidUserSysTimeReader.readDelta(false, null);
+            mCpuUidFreqTimeReader.readDelta(false, null);
             mNumAllUidCpuTimeReads += 2;
             if (mConstants.TRACK_CPU_ACTIVE_CLUSTER_TIME) {
-                mCpuUidActiveTimeReader.readDelta(null);
-                mCpuUidClusterTimeReader.readDelta(null);
+                mCpuUidActiveTimeReader.readDelta(false, null);
+                mCpuUidClusterTimeReader.readDelta(false, null);
                 mNumAllUidCpuTimeReads += 2;
             }
             for (int cluster = mKernelCpuSpeedReaders.length - 1; cluster >= 0; --cluster) {
@@ -12897,7 +12897,7 @@
         final long startTimeMs = mClocks.uptimeMillis();
         final long elapsedRealtimeMs = mClocks.elapsedRealtime();
 
-        mCpuUidUserSysTimeReader.readDelta((uid, timesUs) -> {
+        mCpuUidUserSysTimeReader.readDelta(false, (uid, timesUs) -> {
             long userTimeUs = timesUs[0], systemTimeUs = timesUs[1];
 
             uid = mapUid(uid);
@@ -13011,7 +13011,7 @@
         final long startTimeMs = mClocks.uptimeMillis();
         final long elapsedRealtimeMs = mClocks.elapsedRealtime();
         final List<Integer> uidsToRemove = new ArrayList<>();
-        mCpuUidFreqTimeReader.readDelta((uid, cpuFreqTimeMs) -> {
+        mCpuUidFreqTimeReader.readDelta(false, (uid, cpuFreqTimeMs) -> {
             uid = mapUid(uid);
             if (Process.isIsolated(uid)) {
                 uidsToRemove.add(uid);
@@ -13129,7 +13129,7 @@
         final long startTimeMs = mClocks.uptimeMillis();
         final long elapsedRealtimeMs = mClocks.elapsedRealtime();
         final List<Integer> uidsToRemove = new ArrayList<>();
-        mCpuUidActiveTimeReader.readDelta((uid, cpuActiveTimesMs) -> {
+        mCpuUidActiveTimeReader.readDelta(false, (uid, cpuActiveTimesMs) -> {
             uid = mapUid(uid);
             if (Process.isIsolated(uid)) {
                 uidsToRemove.add(uid);
@@ -13163,7 +13163,7 @@
         final long startTimeMs = mClocks.uptimeMillis();
         final long elapsedRealtimeMs = mClocks.elapsedRealtime();
         final List<Integer> uidsToRemove = new ArrayList<>();
-        mCpuUidClusterTimeReader.readDelta((uid, cpuClusterTimesMs) -> {
+        mCpuUidClusterTimeReader.readDelta(false, (uid, cpuClusterTimesMs) -> {
             uid = mapUid(uid);
             if (Process.isIsolated(uid)) {
                 uidsToRemove.add(uid);
diff --git a/core/java/com/android/internal/os/CpuPowerCalculator.java b/core/java/com/android/internal/os/CpuPowerCalculator.java
index 45d8128..97f727b 100644
--- a/core/java/com/android/internal/os/CpuPowerCalculator.java
+++ b/core/java/com/android/internal/os/CpuPowerCalculator.java
@@ -124,15 +124,15 @@
         long durationMs = (u.getUserCpuTimeUs(statsType) + u.getSystemCpuTimeUs(statsType)) / 1000;
 
         // Constant battery drain when CPU is active
-        double powerMah = mCpuActivePowerEstimator.calculatePower(u.getCpuActiveTime());
+        double powerMah = calculateActiveCpuPowerMah(u.getCpuActiveTime());
 
         // Additional per-cluster battery drain
         long[] cpuClusterTimes = u.getCpuClusterTimes();
         if (cpuClusterTimes != null) {
             if (cpuClusterTimes.length == mNumCpuClusters) {
                 for (int cluster = 0; cluster < mNumCpuClusters; cluster++) {
-                    double power = mPerClusterPowerEstimators[cluster]
-                            .calculatePower(cpuClusterTimes[cluster]);
+                    double power = calculatePerCpuClusterPowerMah(cluster,
+                            cpuClusterTimes[cluster]);
                     powerMah += power;
                     if (DEBUG) {
                         Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + cluster
@@ -151,8 +151,8 @@
             final int speedsForCluster = mPerCpuFreqPowerEstimators[cluster].length;
             for (int speed = 0; speed < speedsForCluster; speed++) {
                 final long timeUs = u.getTimeAtCpuSpeed(cluster, speed, statsType);
-                final double power =
-                        mPerCpuFreqPowerEstimators[cluster][speed].calculatePower(timeUs / 1000);
+                final double power = calculatePerCpuFreqPowerMah(cluster, speed,
+                        timeUs / 1000);
                 if (DEBUG) {
                     Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + cluster + " step #"
                             + speed + " timeUs=" + timeUs + " power="
@@ -207,4 +207,39 @@
         result.powerMah = powerMah;
         result.packageWithHighestDrain = packageWithHighestDrain;
     }
+
+    /**
+     * Calculates active CPU power consumption.
+     *
+     * @param durationsMs duration of CPU usage.
+     * @return a double in milliamp-hours of estimated active CPU power consumption.
+     */
+    public double calculateActiveCpuPowerMah(long durationsMs) {
+        return mCpuActivePowerEstimator.calculatePower(durationsMs);
+    }
+
+    /**
+     * Calculates CPU cluster power consumption.
+     *
+     * @param cluster CPU cluster used.
+     * @param clusterDurationMs duration of CPU cluster usage.
+     * @return a double in milliamp-hours of estimated CPU cluster power consumption.
+     */
+    public double calculatePerCpuClusterPowerMah(int cluster, long clusterDurationMs) {
+        return mPerClusterPowerEstimators[cluster].calculatePower(clusterDurationMs);
+    }
+
+    /**
+     * Calculates CPU cluster power consumption at a specific speedstep.
+     *
+     * @param cluster CPU cluster used.
+     * @param speedStep which speedstep used.
+     * @param clusterSpeedDurationsMs duration of CPU cluster usage at the specified speed step.
+     * @return a double in milliamp-hours of estimated CPU cluster-speed power consumption.
+     */
+    public double calculatePerCpuFreqPowerMah(int cluster, int speedStep,
+            long clusterSpeedDurationsMs) {
+        return mPerCpuFreqPowerEstimators[cluster][speedStep].calculatePower(
+                clusterSpeedDurationsMs);
+    }
 }
diff --git a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
index f7fad2c..4299f09 100644
--- a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
+++ b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
@@ -91,15 +91,24 @@
      * Reads the proc file, calling into the callback with a delta of time for each UID.
      *
      * @param cb The callback to invoke for each line of the proc file. If null,the data is
-     *           consumed and subsequent calls to readDelta will provide a fresh delta.
      */
     public void readDelta(@Nullable Callback<T> cb) {
+        readDelta(false, cb);
+    }
+
+    /**
+     * Reads the proc file, calling into the callback with a delta of time for each UID.
+     *
+     * @param force Ignore the throttling and force read the delta.
+     * @param cb The callback to invoke for each line of the proc file. If null,the data is
+     */
+    public void readDelta(boolean force, @Nullable Callback<T> cb) {
         if (!mThrottle) {
             readDeltaImpl(cb);
             return;
         }
         final long currTimeMs = SystemClock.elapsedRealtime();
-        if (currTimeMs < mLastReadTimeMs + mMinTimeBetweenRead) {
+        if (!force && currTimeMs < mLastReadTimeMs + mMinTimeBetweenRead) {
             if (DEBUG) {
                 Slog.d(mTag, "Throttle readDelta");
             }
diff --git a/core/java/com/android/server/BootReceiver.java b/core/java/com/android/server/BootReceiver.java
index 95999a7..fe3042d 100644
--- a/core/java/com/android/server/BootReceiver.java
+++ b/core/java/com/android/server/BootReceiver.java
@@ -74,6 +74,7 @@
     private static final int GMSCORE_LASTK_LOG_SIZE = 196608;
 
     private static final String TAG_TOMBSTONE = "SYSTEM_TOMBSTONE";
+    private static final String TAG_TOMBSTONE_PROTO = "SYSTEM_TOMBSTONE_PROTO";
 
     // The pre-froyo package and class of the system updater, which
     // ran in the system process.  We need to remove its packages here
@@ -251,14 +252,14 @@
      * @param ctx Context
      * @param tombstone path to the tombstone
      */
-    public static void addTombstoneToDropBox(Context ctx, File tombstone) {
+    public static void addTombstoneToDropBox(Context ctx, File tombstone, boolean proto) {
         final DropBoxManager db = ctx.getSystemService(DropBoxManager.class);
         final String bootReason = SystemProperties.get("ro.boot.bootreason", null);
         HashMap<String, Long> timestamps = readTimestamps();
         try {
             final String headers = getBootHeadersToLogAndUpdate();
             addFileToDropBox(db, timestamps, headers, tombstone.getPath(), LOG_SIZE,
-                    TAG_TOMBSTONE);
+                    proto ? TAG_TOMBSTONE_PROTO : TAG_TOMBSTONE);
         } catch (IOException e) {
             Slog.e(TAG, "Can't log tombstone", e);
         }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index fe4106d..c61802d 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1313,11 +1313,13 @@
         android:protectionLevel="dangerous|instant" />
 
     <!-- ====================================================================== -->
-    <!-- Permissions for accessing the UCE Service                              -->
+    <!-- Permissions for accessing the vendor UCE Service                              -->
     <!-- ====================================================================== -->
 
     <!-- @hide Allows an application to Access UCE-Presence.
          <p>Protection level: signature|privileged
+         @deprecated Framework should no longer use this permission to access the vendor UCE service
+         using AIDL, it is instead implemented by RcsCapabilityExchangeImplBase
     -->
     <permission android:name="android.permission.ACCESS_UCE_PRESENCE_SERVICE"
         android:permissionGroup="android.permission-group.PHONE"
@@ -1325,6 +1327,8 @@
 
     <!-- @hide Allows an application to Access UCE-OPTIONS.
          <p>Protection level: signature|privileged
+         @deprecated Framework should no longer use this permission to access the vendor UCE service
+         using AIDL, it is instead implemented by RcsCapabilityExchangeImplBase
     -->
     <permission android:name="android.permission.ACCESS_UCE_OPTIONS_SERVICE"
         android:permissionGroup="android.permission-group.PHONE"
@@ -2278,6 +2282,11 @@
     <permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi Allows read access to privileged network state in the device config.
+         @hide Used internally. -->
+    <permission android:name="android.permission.READ_NETWORK_DEVICE_CONFIG"
+        android:protectionLevel="signature|privileged" />
+
     <!-- Allows to read device identifiers and use ICC based authentication like EAP-AKA.
          Often required in authentication to access the carrier's server and manage services
          of the subscriber.
@@ -2463,6 +2472,15 @@
     <permission android:name="android.permission.BIND_GBA_SERVICE"
         android:protectionLevel="signature" />
 
+    <!-- Required for an Application to access APIs related to RCS User Capability Exchange.
+         <p> This permission is only granted to system applications fulfilling the SMS, Dialer, and
+         Contacts app roles.
+         <p>Protection level: internal|role
+         @SystemApi
+         @hide -->
+    <permission android:name="android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE"
+        android:protectionLevel="internal|role" />
+
     <!-- ================================== -->
     <!-- Permissions for sdcard interaction -->
     <!-- ================================== -->
@@ -3887,6 +3905,12 @@
     <permission android:name="android.permission.SET_KEYBOARD_LAYOUT"
         android:protectionLevel="signature" />
 
+    <!-- Allows an app to use exact alarm scheduling APIs to perform timing
+         sensitive background work.
+     -->
+    <permission android:name="android.permission.SCHEDULE_EXACT_ALARM"
+        android:protectionLevel="normal|appop"/>
+
     <!-- Allows an application to query tablet mode state and monitor changes
          in it.
          <p>Not for use by third-party applications.
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index e7c4947..59c260c 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -240,77 +240,114 @@
 
     <color name="conversation_important_highlight">#F9AB00</color>
 
-    <!-- Lightest shade of the main color used by the system. White.
+    <!-- Lightest shade of the primary color used by the system. White.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_main_0">#ffffff</color>
-    <!-- Shade of the main system color at 95% lightness.
+    <color name="system_primary_0">#ffffff</color>
+    <!-- Shade of the primary system color at 95% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_main_50">#f2f2f2</color>
-    <!-- Shade of the main system color at 90% lightness.
+    <color name="system_primary_50">#f2f2f2</color>
+    <!-- Shade of the primary system color at 90% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_main_100">#e3e3e3</color>
-    <!-- Shade of the main system color at 80% lightness.
+    <color name="system_primary_100">#e3e3e3</color>
+    <!-- Shade of the primary system color at 80% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_main_200">#c7c7c7</color>
-    <!-- Shade of the main system color at 70% lightness.
+    <color name="system_primary_200">#c7c7c7</color>
+    <!-- Shade of the primary system color at 70% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_main_300">#ababab</color>
-    <!-- Shade of the main system color at 60% lightness.
+    <color name="system_primary_300">#ababab</color>
+    <!-- Shade of the primary system color at 60% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_main_400">#8f8f8f</color>
-    <!-- Shade of the main system color at 50% lightness.
+    <color name="system_primary_400">#8f8f8f</color>
+    <!-- Shade of the primary system color at 50% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_main_500">#757575</color>
-    <!-- Shade of the main system color at 40% lightness.
+    <color name="system_primary_500">#757575</color>
+    <!-- Shade of the primary system color at 40% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_main_600">#5e5e5e</color>
-    <!-- Shade of the main system color at 30% lightness.
+    <color name="system_primary_600">#5e5e5e</color>
+    <!-- Shade of the primary system color at 30% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_main_700">#474747</color>
-    <!-- Shade of the main system color at 20% lightness.
+    <color name="system_primary_700">#474747</color>
+    <!-- Shade of the primary system color at 20% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_main_800">#303030</color>
-    <!-- Shade of the main system color at 10% lightness.
+    <color name="system_primary_800">#303030</color>
+    <!-- Shade of the primary system color at 10% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_main_900">#1f1f1f</color>
-    <!-- Darkest shade of the main color used by the system. Black.
+    <color name="system_primary_900">#1f1f1f</color>
+    <!-- Darkest shade of the primary color used by the system. Black.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_main_1000">#000000</color>
+    <color name="system_primary_1000">#000000</color>
 
-    <!-- Lightest shade of the accent color used by the system. White.
+    <!-- Lightest shade of the secondary color used by the system. White.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent_0">#ffffff</color>
-    <!-- Shade of the accent system color at 95% lightness.
+    <color name="system_secondary_0">#ffffff</color>
+    <!-- Shade of the secondary system color at 95% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent_50">#91fff4</color>
-    <!-- Shade of the accent system color at 90% lightness.
+    <color name="system_secondary_50">#91fff4</color>
+    <!-- Shade of the secondary system color at 90% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent_100">#83f6e5</color>
-    <!-- Shade of the accent system color at 80% lightness.
+    <color name="system_secondary_100">#83f6e5</color>
+    <!-- Shade of the secondary system color at 80% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent_200">#65d9c9</color>
-    <!-- Shade of the accent system color at 70% lightness.
+    <color name="system_secondary_200">#65d9c9</color>
+    <!-- Shade of the secondary system color at 70% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent_300">#45bdae</color>
-    <!-- Shade of the accent system color at 60% lightness.
+    <color name="system_secondary_300">#45bdae</color>
+    <!-- Shade of the secondary system color at 60% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent_400">#1fa293</color>
-    <!-- Shade of the accent system color at 50% lightness.
+    <color name="system_secondary_400">#1fa293</color>
+    <!-- Shade of the secondary system color at 50% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent_500">#008377</color>
-    <!-- Shade of the accent system color at 40% lightness.
+    <color name="system_secondary_500">#008377</color>
+    <!-- Shade of the secondary system color at 40% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent_600">#006d61</color>
-    <!-- Shade of the accent system color at 30% lightness.
+    <color name="system_secondary_600">#006d61</color>
+    <!-- Shade of the secondary system color at 30% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent_700">#005449</color>
-    <!-- Shade of the accent system color at 20% lightness.
+    <color name="system_secondary_700">#005449</color>
+    <!-- Shade of the secondary system color at 20% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent_800">#003c33</color>
-    <!-- Shade of the accent system color at 10% lightness.
+    <color name="system_secondary_800">#003c33</color>
+    <!-- Shade of the secondary system color at 10% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent_900">#00271e</color>
-    <!-- Darkest shade of the accent color used by the system. Black.
+    <color name="system_secondary_900">#00271e</color>
+    <!-- Darkest shade of the secondary color used by the system. Black.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent_1000">#000000</color>
+    <color name="system_secondary_1000">#000000</color>
+
+    <!-- Lightest shade of the neutral color used by the system. White.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_neutral_0">#ffffff</color>
+    <!-- Shade of the neutral system color at 95% lightness.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_neutral_50">#f0f0f0</color>
+    <!-- Shade of the neutral system color at 90% lightness.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_neutral_100">#e2e2e2</color>
+    <!-- Shade of the neutral system color at 80% lightness.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_neutral_200">#c6c6c6</color>
+    <!-- Shade of the neutral system color at 70% lightness.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_neutral_300">#ababab</color>
+    <!-- Shade of the neutral system color at 60% lightness.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_neutral_400">#909090</color>
+    <!-- Shade of the neutral system color at 50% lightness.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_neutral_500">#757575</color>
+    <!-- Shade of the neutral system color at 40% lightness.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_neutral_600">#5e5e5e</color>
+    <!-- Shade of the neutral system color at 30% lightness.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_neutral_700">#464646</color>
+    <!-- Shade of the neutral system color at 20% lightness.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_neutral_800">#303030</color>
+    <!-- Shade of the neutral system color at 10% lightness.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_neutral_900">#1b1b1b</color>
+    <!-- Darkest shade of the neutral color used by the system. Black.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_neutral_1000">#000000</color>
 </resources>
diff --git a/core/res/res/values/colors_device_defaults.xml b/core/res/res/values/colors_device_defaults.xml
index f723426..1020c14 100644
--- a/core/res/res/values/colors_device_defaults.xml
+++ b/core/res/res/values/colors_device_defaults.xml
@@ -17,9 +17,9 @@
 <!-- Colors specific to DeviceDefault themes. These are mostly pass-throughs to enable
      overlaying new theme colors. -->
 <resources>
-    <color name="primary_device_default_dark">@color/system_main_800</color>
-    <color name="primary_device_default_light">@color/system_main_50</color>
-    <color name="primary_device_default_settings">@color/system_main_800</color>
+    <color name="primary_device_default_dark">@color/system_primary_800</color>
+    <color name="primary_device_default_light">@color/system_primary_50</color>
+    <color name="primary_device_default_settings">@color/system_primary_800</color>
     <color name="primary_device_default_settings_light">@color/primary_device_default_light</color>
     <color name="primary_dark_device_default_dark">@color/primary_device_default_dark</color>
     <color name="primary_dark_device_default_light">@color/primary_device_default_light</color>
@@ -33,21 +33,21 @@
     <color name="tertiary_device_default_settings">@color/tertiary_material_settings</color>
     <color name="quaternary_device_default_settings">@color/quaternary_material_settings</color>
 
-    <color name="accent_device_default_light">@color/system_accent_600</color>
-    <color name="accent_device_default_dark">@color/system_accent_200</color>
+    <color name="accent_device_default_light">@color/system_secondary_600</color>
+    <color name="accent_device_default_dark">@color/system_secondary_200</color>
     <color name="accent_device_default">@color/accent_device_default_light</color>
 
-    <color name="background_device_default_dark">@color/system_main_800</color>
-    <color name="background_device_default_light">@color/system_main_50</color>
-    <color name="background_floating_device_default_dark">@color/system_main_900</color>
-    <color name="background_floating_device_default_light">@color/system_main_100</color>
+    <color name="background_device_default_dark">@color/system_primary_800</color>
+    <color name="background_device_default_light">@color/system_primary_50</color>
+    <color name="background_floating_device_default_dark">@color/system_primary_900</color>
+    <color name="background_floating_device_default_light">@color/system_primary_100</color>
 
-    <color name="text_color_primary_device_default_light">@color/system_main_900</color>
-    <color name="text_color_primary_device_default_dark">@color/system_main_50</color>
-    <color name="text_color_secondary_device_default_light">@color/system_main_700</color>
-    <color name="text_color_secondary_device_default_dark">@color/system_main_200</color>
-    <color name="text_color_tertiary_device_default_light">@color/system_main_500</color>
-    <color name="text_color_tertiary_device_default_dark">@color/system_main_400</color>
+    <color name="text_color_primary_device_default_light">@color/system_primary_900</color>
+    <color name="text_color_primary_device_default_dark">@color/system_primary_50</color>
+    <color name="text_color_secondary_device_default_light">@color/system_primary_700</color>
+    <color name="text_color_secondary_device_default_dark">@color/system_primary_200</color>
+    <color name="text_color_tertiary_device_default_light">@color/system_primary_500</color>
+    <color name="text_color_tertiary_device_default_dark">@color/system_primary_400</color>
     <color name="foreground_device_default_light">@color/text_color_primary_device_default_light</color>
     <color name="foreground_device_default_dark">@color/text_color_primary_device_default_dark</color>
 
@@ -55,8 +55,8 @@
     <color name="error_color_device_default_dark">@color/error_color_material_dark</color>
     <color name="error_color_device_default_light">@color/error_color_material_light</color>
 
-    <color name="list_divider_color_light">@color/system_main_500</color>
-    <color name="list_divider_color_dark">@color/system_main_400</color>
+    <color name="list_divider_color_light">@color/system_primary_500</color>
+    <color name="list_divider_color_dark">@color/system_primary_400</color>
     <color name="list_divider_opacity_device_default_light">@android:color/white</color>
     <color name="list_divider_opacity_device_default_dark">@android:color/white</color>
 
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index dd048f3..faa2157 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1948,6 +1948,8 @@
     <string name="config_systemAutomotiveCluster" translatable="false"></string>
     <!-- The name of the package that will hold the system shell role. -->
     <string name="config_systemShell" translatable="false">com.android.shell</string>
+    <!-- The name of the package that will hold the system contacts role. -->
+    <string name="config_systemContacts" translatable="false">com.android.contacts</string>
 
     <!-- The name of the package that will be allowed to change its components' label/icon. -->
     <string name="config_overrideComponentUiPackage" translatable="false"></string>
@@ -2509,10 +2511,9 @@
     <string name="config_ethernet_tcp_buffers" translatable="false">524288,1048576,3145728,524288,1048576,2097152</string>
 
     <!-- What source to use to estimate link upstream and downstream bandwidth capacities.
-         Default is carrier_config, but it should be set to modem if the modem is returning
-         predictive (instead of instantaneous) bandwidth estimate.
-         Values are carrier_config and modem. -->
-    <string name="config_bandwidthEstimateSource">carrier_config</string>
+         Default is bandwidth_estimator.
+         Values are bandwidth_estimator, carrier_config and modem. -->
+    <string name="config_bandwidthEstimateSource">bandwidth_estimator</string>
 
     <!-- Whether WiFi display is supported by this device.
          There are many prerequisites for this feature to work correctly.
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 1ebcf77..22dce9b 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3086,32 +3086,45 @@
     <!-- color definitions go here -->
 
     <!-- Material design dynamic system palette:-->
-    <!-- Dominant color -->
-    <public name="system_main_0" />
-    <public name="system_main_50" />
-    <public name="system_main_100" />
-    <public name="system_main_200" />
-    <public name="system_main_300" />
-    <public name="system_main_400" />
-    <public name="system_main_500" />
-    <public name="system_main_600" />
-    <public name="system_main_700" />
-    <public name="system_main_800" />
-    <public name="system_main_900" />
-    <public name="system_main_1000" />
-    <!-- Accent color -->
-    <public name="system_accent_0" />
-    <public name="system_accent_50" />
-    <public name="system_accent_100" />
-    <public name="system_accent_200" />
-    <public name="system_accent_300" />
-    <public name="system_accent_400" />
-    <public name="system_accent_500" />
-    <public name="system_accent_600" />
-    <public name="system_accent_700" />
-    <public name="system_accent_800" />
-    <public name="system_accent_900" />
-    <public name="system_accent_1000" />
+    <!-- Primary color -->
+    <public name="system_primary_0" />
+    <public name="system_primary_50" />
+    <public name="system_primary_100" />
+    <public name="system_primary_200" />
+    <public name="system_primary_300" />
+    <public name="system_primary_400" />
+    <public name="system_primary_500" />
+    <public name="system_primary_600" />
+    <public name="system_primary_700" />
+    <public name="system_primary_800" />
+    <public name="system_primary_900" />
+    <public name="system_primary_1000" />
+    <!-- Secondary color -->
+    <public name="system_secondary_0" />
+    <public name="system_secondary_50" />
+    <public name="system_secondary_100" />
+    <public name="system_secondary_200" />
+    <public name="system_secondary_300" />
+    <public name="system_secondary_400" />
+    <public name="system_secondary_500" />
+    <public name="system_secondary_600" />
+    <public name="system_secondary_700" />
+    <public name="system_secondary_800" />
+    <public name="system_secondary_900" />
+    <public name="system_secondary_1000" />
+    <!-- Neutral color -->
+    <public name="system_neutral_0" />
+    <public name="system_neutral_50" />
+    <public name="system_neutral_100" />
+    <public name="system_neutral_200" />
+    <public name="system_neutral_300" />
+    <public name="system_neutral_400" />
+    <public name="system_neutral_500" />
+    <public name="system_neutral_600" />
+    <public name="system_neutral_700" />
+    <public name="system_neutral_800" />
+    <public name="system_neutral_900" />
+    <public name="system_neutral_1000" />
   </public-group>
 
   <public-group type="dimen" first-id="0x01050008">
@@ -3138,6 +3151,8 @@
     <public name="config_systemAutomotiveProjection" />
     <!-- @hide @SystemApi -->
     <public name="config_systemShell" />
+    <!-- @hide @SystemApi -->
+    <public name="config_systemContacts" />
   </public-group>
 
   <public-group type="id" first-id="0x01020055">
diff --git a/core/tests/coretests/src/android/app/people/PeopleManagerTest.java b/core/tests/coretests/src/android/app/people/PeopleManagerTest.java
new file mode 100644
index 0000000..a2afc77
--- /dev/null
+++ b/core/tests/coretests/src/android/app/people/PeopleManagerTest.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.people;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.pm.ShortcutInfo;
+import android.os.test.TestLooper;
+import android.util.Pair;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+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;
+
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * Tests for {@link android.app.people.PeopleManager.ConversationListener} and relevant APIs.
+ */
+@RunWith(AndroidJUnit4.class)
+public class PeopleManagerTest {
+
+    private static final String CONVERSATION_ID_1 = "12";
+    private static final String CONVERSATION_ID_2 = "123";
+
+    private Context mContext;
+
+    private final TestLooper mTestLooper = new TestLooper();
+
+    @Mock
+    private IPeopleManager mService;
+    private PeopleManager mPeopleManager;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getContext();
+        MockitoAnnotations.initMocks(this);
+
+        mPeopleManager = new PeopleManager(mContext, mService);
+    }
+
+    @Test
+    public void testCorrectlyMapsToProxyConversationListener() throws Exception {
+        PeopleManager.ConversationListener listenerForConversation1 = mock(
+                PeopleManager.ConversationListener.class);
+        registerListener(CONVERSATION_ID_1, listenerForConversation1);
+        PeopleManager.ConversationListener listenerForConversation2 = mock(
+                PeopleManager.ConversationListener.class);
+        registerListener(CONVERSATION_ID_2, listenerForConversation2);
+
+        Map<PeopleManager.ConversationListener, Pair<Executor, IConversationListener>>
+                listenersToProxy =
+                mPeopleManager.mConversationListeners;
+        Pair<Executor, IConversationListener> listener = listenersToProxy.get(
+                listenerForConversation1);
+        ConversationChannel conversation = getConversation(CONVERSATION_ID_1);
+        listener.second.onConversationUpdate(getConversation(CONVERSATION_ID_1));
+        mTestLooper.dispatchAll();
+
+        // Only call the associated listener.
+        verify(listenerForConversation2, never()).onConversationUpdate(any());
+        // Should update the listeners mapped to the proxy.
+        ArgumentCaptor<ConversationChannel> capturedConversation = ArgumentCaptor.forClass(
+                ConversationChannel.class);
+        verify(listenerForConversation1, times(1)).onConversationUpdate(
+                capturedConversation.capture());
+        ConversationChannel conversationChannel = capturedConversation.getValue();
+        assertEquals(conversationChannel.getShortcutInfo().getId(), CONVERSATION_ID_1);
+        assertEquals(conversationChannel.getShortcutInfo().getLabel(),
+                conversation.getShortcutInfo().getLabel());
+    }
+
+    private ConversationChannel getConversation(String shortcutId) {
+        ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(mContext,
+                shortcutId).setLongLabel(
+                "name").build();
+        NotificationChannel notificationChannel = new NotificationChannel("123",
+                "channel",
+                NotificationManager.IMPORTANCE_DEFAULT);
+        return new ConversationChannel(shortcutInfo, 0,
+                notificationChannel, null,
+                123L, false);
+    }
+
+    private void registerListener(String conversationId,
+            PeopleManager.ConversationListener listener) {
+        mPeopleManager.registerConversationListener(mContext.getPackageName(), mContext.getUserId(),
+                conversationId, listener,
+                mTestLooper.getNewExecutor());
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java
index 8f81ea2..7dca0cb 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java
@@ -93,35 +93,62 @@
         mReader.setThrottle(500);
 
         writeToFile(uidLines(mUids, mInitialTimes));
-        mReader.readDelta(mCallback);
+        mReader.readDelta(false, mCallback);
         assertEquals(6, mCallback.mData.size());
 
         long[][] times1 = increaseTime(mInitialTimes);
         writeToFile(uidLines(mUids, times1));
         mCallback.clear();
-        mReader.readDelta(mCallback);
+        mReader.readDelta(false, mCallback);
         assertEquals(0, mCallback.mData.size());
 
+        // TODO(b/180473895): Replace sleeps with injected simulated time.
         SystemClock.sleep(600);
 
         long[][] times2 = increaseTime(times1);
         writeToFile(uidLines(mUids, times2));
         mCallback.clear();
-        mReader.readDelta(mCallback);
+        mReader.readDelta(false, mCallback);
         assertEquals(6, mCallback.mData.size());
 
         long[][] times3 = increaseTime(times2);
         writeToFile(uidLines(mUids, times3));
         mCallback.clear();
-        mReader.readDelta(mCallback);
+        mReader.readDelta(false, mCallback);
         assertEquals(0, mCallback.mData.size());
+
+        // Force the delta read, previously skipped increments should now be read
+        mCallback.clear();
+        mReader.readDelta(true, mCallback);
+        assertEquals(6, mCallback.mData.size());
+
+        SystemClock.sleep(600);
+
+        long[][] times4 = increaseTime(times3);
+        writeToFile(uidLines(mUids, times4));
+        mCallback.clear();
+        mReader.readDelta(true, mCallback);
+        assertEquals(6, mCallback.mData.size());
+
+        // Don't force the delta read, throttle should be set from last read.
+        long[][] times5 = increaseTime(times4);
+        writeToFile(uidLines(mUids, times5));
+        mCallback.clear();
+        mReader.readDelta(false, mCallback);
+        assertEquals(0, mCallback.mData.size());
+
+        SystemClock.sleep(600);
+
+        mCallback.clear();
+        mReader.readDelta(false, mCallback);
+        assertEquals(6, mCallback.mData.size());
     }
 
     @Test
     public void testReadDelta() throws Exception {
         final long[][] times1 = mInitialTimes;
         writeToFile(uidLines(mUids, times1));
-        mReader.readDelta(mCallback);
+        mReader.readDelta(false, mCallback);
         for (int i = 0; i < mUids.length; i++) {
             mCallback.verify(mUids[i], times1[i]);
         }
@@ -131,7 +158,7 @@
         // Verify that a second call will only return deltas.
         final long[][] times2 = increaseTime(times1);
         writeToFile(uidLines(mUids, times2));
-        mReader.readDelta(mCallback);
+        mReader.readDelta(false, mCallback);
         for (int i = 0; i < mUids.length; i++) {
             mCallback.verify(mUids[i], subtract(times2[i], times1[i]));
         }
@@ -139,20 +166,20 @@
         mCallback.clear();
 
         // Verify that there won't be a callback if the proc file values didn't change.
-        mReader.readDelta(mCallback);
+        mReader.readDelta(false, mCallback);
         mCallback.verifyNoMoreInteractions();
         mCallback.clear();
 
         // Verify that calling with a null callback doesn't result in any crashes
         final long[][] times3 = increaseTime(times2);
         writeToFile(uidLines(mUids, times3));
-        mReader.readDelta(null);
+        mReader.readDelta(false, null);
 
         // Verify that the readDelta call will only return deltas when
         // the previous call had null callback.
         final long[][] times4 = increaseTime(times3);
         writeToFile(uidLines(mUids, times4));
-        mReader.readDelta(mCallback);
+        mReader.readDelta(false, mCallback);
         for (int i = 0; i < mUids.length; i++) {
             mCallback.verify(mUids[i], subtract(times4[i], times3[i]));
         }
@@ -165,7 +192,7 @@
     public void testReadDeltaWrongData() throws Exception {
         final long[][] times1 = mInitialTimes;
         writeToFile(uidLines(mUids, times1));
-        mReader.readDelta(mCallback);
+        mReader.readDelta(false, mCallback);
         for (int i = 0; i < mUids.length; i++) {
             mCallback.verify(mUids[i], times1[i]);
         }
@@ -176,7 +203,7 @@
         final long[][] times2 = increaseTime(times1);
         times2[0][0] = 1000;
         writeToFile(uidLines(mUids, times2));
-        mReader.readDelta(mCallback);
+        mReader.readDelta(false, mCallback);
         for (int i = 1; i < mUids.length; i++) {
             mCallback.verify(mUids[i], subtract(times2[i], times1[i]));
         }
diff --git a/data/etc/com.android.emergency.xml b/data/etc/com.android.emergency.xml
index fa92b6d..2d6ae2e 100644
--- a/data/etc/com.android.emergency.xml
+++ b/data/etc/com.android.emergency.xml
@@ -21,5 +21,7 @@
         <permission name="android.permission.MANAGE_USERS"/>
         <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
         <permission name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/>
+        <!-- Required to update emergency gesture settings -->
+        <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
     </privapp-permissions>
 </permissions>
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 84da930..5633de3 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -259,6 +259,12 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-1834214907": {
+      "message": "createNonAppWindowAnimations()",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_REMOTE_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
+    },
     "-1824578273": {
       "message": "Reporting new frame to %s: %s",
       "level": "VERBOSE",
@@ -811,6 +817,18 @@
       "group": "WM_DEBUG_RECENTS_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RecentsAnimation.java"
     },
+    "-1153814764": {
+      "message": "onAnimationCancelled",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_REMOTE_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/NonAppWindowAnimationAdapter.java"
+    },
+    "-1144293044": {
+      "message": "SURFACE SET FREEZE LAYER: %s",
+      "level": "INFO",
+      "group": "WM_SHOW_TRANSACTIONS",
+      "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
+    },
     "-1142279614": {
       "message": "Looking for focus: %s, flags=%d, canReceive=%b, reason=%s",
       "level": "VERBOSE",
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index 2b0d7e5..c79c12c 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -279,8 +279,8 @@
  * }
  */
 public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAuthArgs {
-
-    private static final X500Principal DEFAULT_CERT_SUBJECT = new X500Principal("CN=fake");
+    private static final X500Principal DEFAULT_CERT_SUBJECT =
+            new X500Principal("CN=Android Keystore Key");
     private static final BigInteger DEFAULT_CERT_SERIAL_NUMBER = new BigInteger("1");
     private static final Date DEFAULT_CERT_NOT_BEFORE = new Date(0L); // Jan 1 1970
     private static final Date DEFAULT_CERT_NOT_AFTER = new Date(2461449600000L); // Jan 1 2048
@@ -317,6 +317,7 @@
     private final boolean mUnlockedDeviceRequired;
     private final boolean mCriticalToDeviceEncryption;
     private final int mMaxUsageCount;
+    private final String mAttestKeyAlias;
     /*
      * ***NOTE***: All new fields MUST also be added to the following:
      * ParcelableKeyGenParameterSpec class.
@@ -358,7 +359,8 @@
             boolean userConfirmationRequired,
             boolean unlockedDeviceRequired,
             boolean criticalToDeviceEncryption,
-            int maxUsageCount) {
+            int maxUsageCount,
+            String attestKeyAlias) {
         if (TextUtils.isEmpty(keyStoreAlias)) {
             throw new IllegalArgumentException("keyStoreAlias must not be empty");
         }
@@ -413,6 +415,7 @@
         mUnlockedDeviceRequired = unlockedDeviceRequired;
         mCriticalToDeviceEncryption = criticalToDeviceEncryption;
         mMaxUsageCount = maxUsageCount;
+        mAttestKeyAlias = attestKeyAlias;
     }
 
     /**
@@ -869,6 +872,18 @@
     }
 
     /**
+     * Returns the alias of the attestation key that will be used to sign the attestation
+     * certificate of the generated key.  Note that an attestation certificate will only be
+     * generated if an attestation challenge is set.
+     *
+     * @see Builder#setAttestKeyAlias(String)
+     */
+    @Nullable
+    public String getAttestKeyAlias() {
+        return mAttestKeyAlias;
+    }
+
+    /**
      * Builder of {@link KeyGenParameterSpec} instances.
      */
     public final static class Builder {
@@ -906,6 +921,7 @@
         private boolean mUnlockedDeviceRequired = false;
         private boolean mCriticalToDeviceEncryption = false;
         private int mMaxUsageCount = KeyProperties.UNRESTRICTED_USAGE_COUNT;
+        private String mAttestKeyAlias = null;
 
         /**
          * Creates a new instance of the {@code Builder}.
@@ -975,6 +991,7 @@
             mUnlockedDeviceRequired = sourceSpec.isUnlockedDeviceRequired();
             mCriticalToDeviceEncryption = sourceSpec.isCriticalToDeviceEncryption();
             mMaxUsageCount = sourceSpec.getMaxUsageCount();
+            mAttestKeyAlias = sourceSpec.getAttestKeyAlias();
         }
 
         /**
@@ -1695,6 +1712,28 @@
         }
 
         /**
+         * Sets the alias of the attestation key that will be used to sign the attestation
+         * certificate for the generated key pair, if an attestation challenge is set with {@link
+         * #setAttestationChallenge}.  If an attestKeyAlias is set but no challenge, {@link
+         * java.security.KeyPairGenerator#initialize} will throw {@link
+         * java.security.InvalidAlgorithmParameterException}.
+         *
+         * <p>If the attestKeyAlias is set to null (the default), Android Keystore will select an
+         * appropriate system-provided attestation signing key.  If not null, the alias must
+         * reference an Android Keystore Key that was created with {@link
+         * android.security.keystore.KeyProperties#PURPOSE_ATTEST_KEY}, or key generation will throw
+         * {@link java.security.InvalidAlgorithmParameterException}.
+         *
+         * @param attestKeyAlias the alias of the attestation key to be used to sign the
+         *        attestation certificate.
+         */
+        @NonNull
+        public Builder setAttestKeyAlias(@Nullable String attestKeyAlias) {
+            mAttestKeyAlias = attestKeyAlias;
+            return this;
+        }
+
+        /**
          * Builds an instance of {@code KeyGenParameterSpec}.
          */
         @NonNull
@@ -1731,7 +1770,8 @@
                     mUserConfirmationRequired,
                     mUnlockedDeviceRequired,
                     mCriticalToDeviceEncryption,
-                    mMaxUsageCount);
+                    mMaxUsageCount,
+                    mAttestKeyAlias);
         }
     }
 }
diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java
index 293ab05..7b0fa91 100644
--- a/keystore/java/android/security/keystore/KeyProperties.java
+++ b/keystore/java/android/security/keystore/KeyProperties.java
@@ -70,6 +70,7 @@
             PURPOSE_VERIFY,
             PURPOSE_WRAP_KEY,
             PURPOSE_AGREE_KEY,
+            PURPOSE_ATTEST_KEY,
     })
     public @interface PurposeEnum {}
 
@@ -113,6 +114,13 @@
     public static final int PURPOSE_AGREE_KEY = 1 << 6;
 
     /**
+     * Purpose of key: Signing attestaions. This purpose is incompatible with all others, meaning
+     * that when generating a key with PURPOSE_ATTEST_KEY, no other purposes may be specified. In
+     * addition, PURPOSE_ATTEST_KEY may not be specified for imported keys.
+     */
+    public static final int PURPOSE_ATTEST_KEY = 1 << 7;
+
+    /**
      * @hide
      */
     public static abstract class Purpose {
@@ -132,6 +140,8 @@
                     return KeymasterDefs.KM_PURPOSE_WRAP;
                 case PURPOSE_AGREE_KEY:
                     return KeymasterDefs.KM_PURPOSE_AGREE_KEY;
+                case PURPOSE_ATTEST_KEY:
+                    return KeymasterDefs.KM_PURPOSE_ATTEST_KEY;
                 default:
                     throw new IllegalArgumentException("Unknown purpose: " + purpose);
             }
@@ -151,6 +161,8 @@
                     return PURPOSE_WRAP_KEY;
                 case KeymasterDefs.KM_PURPOSE_AGREE_KEY:
                     return PURPOSE_AGREE_KEY;
+                case KeymasterDefs.KM_PURPOSE_ATTEST_KEY:
+                    return PURPOSE_ATTEST_KEY;
                 default:
                     throw new IllegalArgumentException("Unknown purpose: " + purpose);
             }
diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java
index aaa3715..fe92270 100644
--- a/keystore/java/android/security/keystore/KeyProtection.java
+++ b/keystore/java/android/security/keystore/KeyProtection.java
@@ -588,7 +588,8 @@
         private long mBoundToSecureUserId = GateKeeper.INVALID_SECURE_USER_ID;
         private boolean mCriticalToDeviceEncryption = false;
         private boolean mIsStrongBoxBacked = false;
-        private  int mMaxUsageCount = KeyProperties.UNRESTRICTED_USAGE_COUNT;
+        private int mMaxUsageCount = KeyProperties.UNRESTRICTED_USAGE_COUNT;
+        private String mAttestKeyAlias = null;
 
         /**
          * Creates a new instance of the {@code Builder}.
diff --git a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
index 1f2f853..c20cf01 100644
--- a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
@@ -110,6 +110,7 @@
         out.writeBoolean(mSpec.isUnlockedDeviceRequired());
         out.writeBoolean(mSpec.isCriticalToDeviceEncryption());
         out.writeInt(mSpec.getMaxUsageCount());
+        out.writeString(mSpec.getAttestKeyAlias());
     }
 
     private static Date readDateOrNull(Parcel in) {
@@ -170,6 +171,7 @@
         final boolean unlockedDeviceRequired = in.readBoolean();
         final boolean criticalToDeviceEncryption = in.readBoolean();
         final int maxUsageCount = in.readInt();
+        final String attestKeyAlias = in.readString();
         // The KeyGenParameterSpec is intentionally not constructed using a Builder here:
         // The intention is for this class to break if new parameters are added to the
         // KeyGenParameterSpec constructor (whereas using a builder would silently drop them).
@@ -205,7 +207,8 @@
                 userConfirmationRequired,
                 unlockedDeviceRequired,
                 criticalToDeviceEncryption,
-                maxUsageCount);
+                maxUsageCount,
+                attestKeyAlias);
     }
 
     public static final @android.annotation.NonNull Creator<ParcelableKeyGenParameterSpec> CREATOR = new Creator<ParcelableKeyGenParameterSpec>() {
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index 4d27c34..b3bfd6a 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -20,7 +20,9 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.security.keymint.KeyParameter;
+import android.hardware.security.keymint.KeyPurpose;
 import android.hardware.security.keymint.SecurityLevel;
+import android.hardware.security.keymint.Tag;
 import android.os.Build;
 import android.security.KeyPairGeneratorSpec;
 import android.security.KeyStore;
@@ -37,9 +39,11 @@
 import android.security.keystore.KeymasterUtils;
 import android.security.keystore.SecureKeyImportUnavailableException;
 import android.security.keystore.StrongBoxUnavailableException;
+import android.system.keystore2.Authorization;
 import android.system.keystore2.Domain;
 import android.system.keystore2.IKeystoreSecurityLevel;
 import android.system.keystore2.KeyDescriptor;
+import android.system.keystore2.KeyEntryResponse;
 import android.system.keystore2.KeyMetadata;
 import android.system.keystore2.ResponseCode;
 import android.telephony.TelephonyManager;
@@ -61,6 +65,7 @@
 import java.security.spec.ECGenParameterSpec;
 import java.security.spec.RSAKeyGenParameterSpec;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -69,6 +74,7 @@
 import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Predicate;
 
 /**
  * Provides a way to create instances of a KeyPair which will be placed in the
@@ -153,6 +159,7 @@
     private int mKeymasterAlgorithm = -1;
     private int mKeySizeBits;
     private SecureRandom mRng;
+    private KeyDescriptor mAttestKeyDescriptor;
 
     private int[] mKeymasterPurposes;
     private int[] mKeymasterBlockModes;
@@ -197,83 +204,9 @@
                 // Legacy/deprecated spec
                 KeyPairGeneratorSpec legacySpec = (KeyPairGeneratorSpec) params;
                 try {
-                    KeyGenParameterSpec.Builder specBuilder;
-                    String specKeyAlgorithm = legacySpec.getKeyType();
-                    if (specKeyAlgorithm != null) {
-                        // Spec overrides the generator's default key algorithm
-                        try {
-                            keymasterAlgorithm =
-                                    KeyProperties.KeyAlgorithm.toKeymasterAsymmetricKeyAlgorithm(
-                                            specKeyAlgorithm);
-                        } catch (IllegalArgumentException e) {
-                            throw new InvalidAlgorithmParameterException(
-                                    "Invalid key type in parameters", e);
-                        }
-                    }
-                    switch (keymasterAlgorithm) {
-                        case KeymasterDefs.KM_ALGORITHM_EC:
-                            specBuilder = new KeyGenParameterSpec.Builder(
-                                    legacySpec.getKeystoreAlias(),
-                                    KeyProperties.PURPOSE_SIGN
-                                    | KeyProperties.PURPOSE_VERIFY);
-                            // Authorized to be used with any digest (including no digest).
-                            // MD5 was never offered for Android Keystore for ECDSA.
-                            specBuilder.setDigests(
-                                    KeyProperties.DIGEST_NONE,
-                                    KeyProperties.DIGEST_SHA1,
-                                    KeyProperties.DIGEST_SHA224,
-                                    KeyProperties.DIGEST_SHA256,
-                                    KeyProperties.DIGEST_SHA384,
-                                    KeyProperties.DIGEST_SHA512);
-                            break;
-                        case KeymasterDefs.KM_ALGORITHM_RSA:
-                            specBuilder = new KeyGenParameterSpec.Builder(
-                                    legacySpec.getKeystoreAlias(),
-                                    KeyProperties.PURPOSE_ENCRYPT
-                                    | KeyProperties.PURPOSE_DECRYPT
-                                    | KeyProperties.PURPOSE_SIGN
-                                    | KeyProperties.PURPOSE_VERIFY);
-                            // Authorized to be used with any digest (including no digest).
-                            specBuilder.setDigests(
-                                    KeyProperties.DIGEST_NONE,
-                                    KeyProperties.DIGEST_MD5,
-                                    KeyProperties.DIGEST_SHA1,
-                                    KeyProperties.DIGEST_SHA224,
-                                    KeyProperties.DIGEST_SHA256,
-                                    KeyProperties.DIGEST_SHA384,
-                                    KeyProperties.DIGEST_SHA512);
-                            // Authorized to be used with any encryption and signature padding
-                            // schemes (including no padding).
-                            specBuilder.setEncryptionPaddings(
-                                    KeyProperties.ENCRYPTION_PADDING_NONE,
-                                    KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1,
-                                    KeyProperties.ENCRYPTION_PADDING_RSA_OAEP);
-                            specBuilder.setSignaturePaddings(
-                                    KeyProperties.SIGNATURE_PADDING_RSA_PKCS1,
-                                    KeyProperties.SIGNATURE_PADDING_RSA_PSS);
-                            // Disable randomized encryption requirement to support encryption
-                            // padding NONE above.
-                            specBuilder.setRandomizedEncryptionRequired(false);
-                            break;
-                        default:
-                            throw new ProviderException(
-                                    "Unsupported algorithm: " + mKeymasterAlgorithm);
-                    }
-
-                    if (legacySpec.getKeySize() != -1) {
-                        specBuilder.setKeySize(legacySpec.getKeySize());
-                    }
-                    if (legacySpec.getAlgorithmParameterSpec() != null) {
-                        specBuilder.setAlgorithmParameterSpec(
-                                legacySpec.getAlgorithmParameterSpec());
-                    }
-                    specBuilder.setCertificateSubject(legacySpec.getSubjectDN());
-                    specBuilder.setCertificateSerialNumber(legacySpec.getSerialNumber());
-                    specBuilder.setCertificateNotBefore(legacySpec.getStartDate());
-                    specBuilder.setCertificateNotAfter(legacySpec.getEndDate());
-                    specBuilder.setUserAuthenticationRequired(false);
-
-                    spec = specBuilder.build();
+                    keymasterAlgorithm = getKeymasterAlgorithmFromLegacy(keymasterAlgorithm,
+                            legacySpec);
+                    spec = buildKeyGenParameterSpecFromLegacy(legacySpec, keymasterAlgorithm);
                 } catch (NullPointerException | IllegalArgumentException e) {
                     throw new InvalidAlgorithmParameterException(e);
                 }
@@ -342,6 +275,10 @@
             mJcaKeyAlgorithm = jcaKeyAlgorithm;
             mRng = random;
             mKeyStore = KeyStore2.getInstance();
+
+            mAttestKeyDescriptor = buildAndCheckAttestKeyDescriptor(spec);
+            checkAttestKeyPurpose(spec);
+
             success = true;
         } finally {
             if (!success) {
@@ -350,6 +287,156 @@
         }
     }
 
+    private void checkAttestKeyPurpose(KeyGenParameterSpec spec)
+            throws InvalidAlgorithmParameterException {
+        if ((spec.getPurposes() & KeyProperties.PURPOSE_ATTEST_KEY) != 0
+                && spec.getPurposes() != KeyProperties.PURPOSE_ATTEST_KEY) {
+            throw new InvalidAlgorithmParameterException(
+                    "PURPOSE_ATTEST_KEY may not be specified with any other purposes");
+        }
+    }
+
+    private KeyDescriptor buildAndCheckAttestKeyDescriptor(KeyGenParameterSpec spec)
+            throws InvalidAlgorithmParameterException {
+        if (spec.getAttestKeyAlias() != null) {
+            KeyDescriptor attestKeyDescriptor = new KeyDescriptor();
+            attestKeyDescriptor.domain = Domain.APP;
+            attestKeyDescriptor.alias = spec.getAttestKeyAlias();
+            try {
+                KeyEntryResponse attestKey = mKeyStore.getKeyEntry(attestKeyDescriptor);
+                checkAttestKeyChallenge(spec);
+                checkAttestKeyPurpose(attestKey.metadata.authorizations);
+                checkAttestKeySecurityLevel(spec, attestKey);
+            } catch (KeyStoreException e) {
+                throw new InvalidAlgorithmParameterException("Invalid attestKeyAlias", e);
+            }
+            return attestKeyDescriptor;
+        }
+        return null;
+    }
+
+    private void checkAttestKeyChallenge(KeyGenParameterSpec spec)
+            throws InvalidAlgorithmParameterException {
+        if (spec.getAttestationChallenge() == null) {
+            throw new InvalidAlgorithmParameterException(
+                    "AttestKey specified but no attestation challenge provided");
+        }
+    }
+
+    private void checkAttestKeyPurpose(Authorization[] keyAuths)
+            throws InvalidAlgorithmParameterException {
+        Predicate<Authorization> isAttestKeyPurpose = x -> x.keyParameter.tag == Tag.PURPOSE
+                && x.keyParameter.value.getKeyPurpose() == KeyPurpose.ATTEST_KEY;
+
+        if (Arrays.stream(keyAuths).noneMatch(isAttestKeyPurpose)) {
+            throw new InvalidAlgorithmParameterException(
+                    ("Invalid attestKey, does not have PURPOSE_ATTEST_KEY"));
+        }
+    }
+
+    private void checkAttestKeySecurityLevel(KeyGenParameterSpec spec, KeyEntryResponse key)
+            throws InvalidAlgorithmParameterException {
+        boolean attestKeyInStrongBox = key.metadata.keySecurityLevel == SecurityLevel.STRONGBOX;
+        if (spec.isStrongBoxBacked() != attestKeyInStrongBox) {
+            if (attestKeyInStrongBox) {
+                throw new InvalidAlgorithmParameterException(
+                        "Invalid security level: Cannot sign non-StrongBox key with "
+                                + "StrongBox attestKey");
+
+            } else {
+                throw new InvalidAlgorithmParameterException(
+                        "Invalid security level: Cannot sign StrongBox key with "
+                                + "non-StrongBox attestKey");
+            }
+        }
+    }
+
+    private int getKeymasterAlgorithmFromLegacy(int keymasterAlgorithm,
+            KeyPairGeneratorSpec legacySpec) throws InvalidAlgorithmParameterException {
+        String specKeyAlgorithm = legacySpec.getKeyType();
+        if (specKeyAlgorithm != null) {
+            // Spec overrides the generator's default key algorithm
+            try {
+                keymasterAlgorithm =
+                        KeyProperties.KeyAlgorithm.toKeymasterAsymmetricKeyAlgorithm(
+                                specKeyAlgorithm);
+            } catch (IllegalArgumentException e) {
+                throw new InvalidAlgorithmParameterException(
+                        "Invalid key type in parameters", e);
+            }
+        }
+        return keymasterAlgorithm;
+    }
+
+    private KeyGenParameterSpec buildKeyGenParameterSpecFromLegacy(KeyPairGeneratorSpec legacySpec,
+            int keymasterAlgorithm) {
+        KeyGenParameterSpec.Builder specBuilder;
+        switch (keymasterAlgorithm) {
+            case KeymasterDefs.KM_ALGORITHM_EC:
+                specBuilder = new KeyGenParameterSpec.Builder(
+                        legacySpec.getKeystoreAlias(),
+                        KeyProperties.PURPOSE_SIGN
+                        | KeyProperties.PURPOSE_VERIFY);
+                // Authorized to be used with any digest (including no digest).
+                // MD5 was never offered for Android Keystore for ECDSA.
+                specBuilder.setDigests(
+                        KeyProperties.DIGEST_NONE,
+                        KeyProperties.DIGEST_SHA1,
+                        KeyProperties.DIGEST_SHA224,
+                        KeyProperties.DIGEST_SHA256,
+                        KeyProperties.DIGEST_SHA384,
+                        KeyProperties.DIGEST_SHA512);
+                break;
+            case KeymasterDefs.KM_ALGORITHM_RSA:
+                specBuilder = new KeyGenParameterSpec.Builder(
+                        legacySpec.getKeystoreAlias(),
+                        KeyProperties.PURPOSE_ENCRYPT
+                        | KeyProperties.PURPOSE_DECRYPT
+                        | KeyProperties.PURPOSE_SIGN
+                        | KeyProperties.PURPOSE_VERIFY);
+                // Authorized to be used with any digest (including no digest).
+                specBuilder.setDigests(
+                        KeyProperties.DIGEST_NONE,
+                        KeyProperties.DIGEST_MD5,
+                        KeyProperties.DIGEST_SHA1,
+                        KeyProperties.DIGEST_SHA224,
+                        KeyProperties.DIGEST_SHA256,
+                        KeyProperties.DIGEST_SHA384,
+                        KeyProperties.DIGEST_SHA512);
+                // Authorized to be used with any encryption and signature padding
+                // schemes (including no padding).
+                specBuilder.setEncryptionPaddings(
+                        KeyProperties.ENCRYPTION_PADDING_NONE,
+                        KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1,
+                        KeyProperties.ENCRYPTION_PADDING_RSA_OAEP);
+                specBuilder.setSignaturePaddings(
+                        KeyProperties.SIGNATURE_PADDING_RSA_PKCS1,
+                        KeyProperties.SIGNATURE_PADDING_RSA_PSS);
+                // Disable randomized encryption requirement to support encryption
+                // padding NONE above.
+                specBuilder.setRandomizedEncryptionRequired(false);
+                break;
+            default:
+                throw new ProviderException(
+                        "Unsupported algorithm: " + mKeymasterAlgorithm);
+        }
+
+        if (legacySpec.getKeySize() != -1) {
+            specBuilder.setKeySize(legacySpec.getKeySize());
+        }
+        if (legacySpec.getAlgorithmParameterSpec() != null) {
+            specBuilder.setAlgorithmParameterSpec(
+                    legacySpec.getAlgorithmParameterSpec());
+        }
+        specBuilder.setCertificateSubject(legacySpec.getSubjectDN());
+        specBuilder.setCertificateSerialNumber(legacySpec.getSerialNumber());
+        specBuilder.setCertificateNotBefore(legacySpec.getStartDate());
+        specBuilder.setCertificateNotAfter(legacySpec.getEndDate());
+        specBuilder.setUserAuthenticationRequired(false);
+
+        return specBuilder.build();
+    }
+
     private void resetAll() {
         mEntryAlias = null;
         mEntryUid = KeyProperties.NAMESPACE_APPLICATION;
@@ -464,7 +551,7 @@
         try {
             KeyStoreSecurityLevel iSecurityLevel = mKeyStore.getSecurityLevel(securityLevel);
 
-            KeyMetadata metadata = iSecurityLevel.generateKey(descriptor, null,
+            KeyMetadata metadata = iSecurityLevel.generateKey(descriptor, mAttestKeyDescriptor,
                     constructKeyGenerationArguments(), flags, additionalEntropy);
 
             AndroidKeyStorePublicKey publicKey =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
index 73371e7..56fe126 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
@@ -75,7 +75,7 @@
      */
     public static final int SPRING_TO_TOUCH_STIFFNESS = 12000;
     public static final float IME_ANIMATION_STIFFNESS = SpringForce.STIFFNESS_LOW;
-    private static final int CHAIN_STIFFNESS = 600;
+    private static final int CHAIN_STIFFNESS = 800;
     public static final float DEFAULT_BOUNCINESS = 0.9f;
 
     private final PhysicsAnimator.SpringConfig mAnimateOutSpringConfig =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 71331df..843e27f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -978,7 +978,8 @@
         finishResizeForMenu(destinationBounds);
     }
 
-    private void finishResizeForMenu(Rect destinationBounds) {
+    /** Moves the PiP menu to the destination bounds. */
+    public void finishResizeForMenu(Rect destinationBounds) {
         mPipMenuController.movePipMenu(null, null, destinationBounds);
         mPipMenuController.updateMenuBounds(destinationBounds);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index c3970e3..4a2a032 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -552,6 +552,7 @@
         // mTouchHandler would rely on the bounds populated from mPipTaskOrganizer
         mPipTaskOrganizer.onMovementBoundsChanged(outBounds, fromRotation, fromImeAdjustment,
                 fromShelfAdjustment, wct);
+        mPipTaskOrganizer.finishResizeForMenu(outBounds);
         mTouchHandler.onMovementBoundsChanged(mTmpInsetBounds, mPipBoundsState.getNormalBounds(),
                 outBounds, fromImeAdjustment, fromShelfAdjustment, rotation);
     }
diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS
index 2c6c7b3..d80699d 100644
--- a/libs/WindowManager/Shell/tests/OWNERS
+++ b/libs/WindowManager/Shell/tests/OWNERS
@@ -1,2 +1,3 @@
+# Bug component: 909476
 # includes OWNERS from parent directories
 natanieljr@google.com
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 385d465..678b0ad 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -445,6 +445,7 @@
         "renderthread/TimeLord.cpp",
         "hwui/AnimatedImageDrawable.cpp",
         "hwui/Bitmap.cpp",
+        "hwui/BlurDrawLooper.cpp",
         "hwui/Canvas.cpp",
         "hwui/ImageDecoder.cpp",
         "hwui/MinikinSkia.cpp",
diff --git a/libs/hwui/FrameInfo.cpp b/libs/hwui/FrameInfo.cpp
index ce9b288..5d3f6f2 100644
--- a/libs/hwui/FrameInfo.cpp
+++ b/libs/hwui/FrameInfo.cpp
@@ -40,6 +40,7 @@
         "DequeueBufferDuration",
         "QueueBufferDuration",
         "GpuCompleted",
+        "SwapBuffersCompleted"
 };
 
 static_assert(static_cast<int>(FrameInfoIndex::NumIndexes) == 20,
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 8fddf71..1fddac4 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -463,9 +463,7 @@
 }
 
 void SkiaCanvas::drawPoint(float x, float y, const Paint& paint) {
-    apply_looper(&paint, [&](const SkPaint& p) {
-        mCanvas->drawPoint(x, y, p);
-    });
+    apply_looper(&paint, [&](const SkPaint& p) { mCanvas->drawPoint(x, y, p); });
 }
 
 void SkiaCanvas::drawPoints(const float* points, int count, const Paint& paint) {
@@ -493,9 +491,7 @@
 
 void SkiaCanvas::drawRegion(const SkRegion& region, const Paint& paint) {
     if (CC_UNLIKELY(paint.nothingToDraw())) return;
-    apply_looper(&paint, [&](const SkPaint& p) {
-        mCanvas->drawRegion(region, p);
-    });
+    apply_looper(&paint, [&](const SkPaint& p) { mCanvas->drawRegion(region, p); });
 }
 
 void SkiaCanvas::drawRoundRect(float left, float top, float right, float bottom, float rx, float ry,
@@ -509,24 +505,18 @@
 
 void SkiaCanvas::drawDoubleRoundRect(const SkRRect& outer, const SkRRect& inner,
                                 const Paint& paint) {
-    apply_looper(&paint, [&](const SkPaint& p) {
-        mCanvas->drawDRRect(outer, inner, p);
-    });
+    apply_looper(&paint, [&](const SkPaint& p) { mCanvas->drawDRRect(outer, inner, p); });
 }
 
 void SkiaCanvas::drawCircle(float x, float y, float radius, const Paint& paint) {
     if (CC_UNLIKELY(radius <= 0 || paint.nothingToDraw())) return;
-    apply_looper(&paint, [&](const SkPaint& p) {
-        mCanvas->drawCircle(x, y, radius, p);
-    });
+    apply_looper(&paint, [&](const SkPaint& p) { mCanvas->drawCircle(x, y, radius, p); });
 }
 
 void SkiaCanvas::drawOval(float left, float top, float right, float bottom, const Paint& paint) {
     if (CC_UNLIKELY(paint.nothingToDraw())) return;
     SkRect oval = SkRect::MakeLTRB(left, top, right, bottom);
-    apply_looper(&paint, [&](const SkPaint& p) {
-        mCanvas->drawOval(oval, p);
-    });
+    apply_looper(&paint, [&](const SkPaint& p) { mCanvas->drawOval(oval, p); });
 }
 
 void SkiaCanvas::drawArc(float left, float top, float right, float bottom, float startAngle,
@@ -547,9 +537,7 @@
     if (CC_UNLIKELY(path.isEmpty() && (!path.isInverseFillType()))) {
         return;
     }
-    apply_looper(&paint, [&](const SkPaint& p) {
-        mCanvas->drawPath(path, p);
-    });
+    apply_looper(&paint, [&](const SkPaint& p) { mCanvas->drawPath(path, p); });
 }
 
 void SkiaCanvas::drawVertices(const SkVertices* vertices, SkBlendMode mode, const Paint& paint) {
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index 155f6df..eac3f22 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -208,29 +208,21 @@
      */
     PaintCoW&& filterPaint(PaintCoW&& paint) const;
 
+    // proc(const SkPaint& modifiedPaint)
     template <typename Proc> void apply_looper(const Paint* paint, Proc proc) {
         SkPaint skp;
-        SkDrawLooper* looper = nullptr;
+        BlurDrawLooper* looper = nullptr;
         if (paint) {
             skp = *filterPaint(paint);
             looper = paint->getLooper();
         }
         if (looper) {
-            SkSTArenaAlloc<256> alloc;
-            SkDrawLooper::Context* ctx = looper->makeContext(&alloc);
-            if (ctx) {
-                SkDrawLooper::Context::Info info;
-                for (;;) {
-                    SkPaint p = skp;
-                    if (!ctx->next(&info, &p)) {
-                        break;
-                    }
-                    mCanvas->save();
-                    mCanvas->translate(info.fTranslate.fX, info.fTranslate.fY);
-                    proc(p);
-                    mCanvas->restore();
-                }
-            }
+            looper->apply(skp, [&](SkPoint offset, const SkPaint& modifiedPaint) {
+                mCanvas->save();
+                mCanvas->translate(offset.fX, offset.fY);
+                proc(modifiedPaint);
+                mCanvas->restore();
+            });
         } else {
             proc(skp);
         }
diff --git a/libs/hwui/hwui/BlurDrawLooper.cpp b/libs/hwui/hwui/BlurDrawLooper.cpp
new file mode 100644
index 0000000..27a038d
--- /dev/null
+++ b/libs/hwui/hwui/BlurDrawLooper.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "BlurDrawLooper.h"
+#include <SkMaskFilter.h>
+
+namespace android {
+
+BlurDrawLooper::BlurDrawLooper(SkColor4f color, float blurSigma, SkPoint offset)
+        : mColor(color), mBlurSigma(blurSigma), mOffset(offset) {}
+
+BlurDrawLooper::~BlurDrawLooper() = default;
+
+SkPoint BlurDrawLooper::apply(SkPaint* paint) const {
+    paint->setColor(mColor);
+    if (mBlurSigma > 0) {
+        paint->setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, mBlurSigma, true));
+    }
+    return mOffset;
+}
+
+sk_sp<BlurDrawLooper> BlurDrawLooper::Make(SkColor4f color, SkColorSpace* cs, float blurSigma,
+                                           SkPoint offset) {
+    if (cs) {
+        SkPaint tmp;
+        tmp.setColor(color, cs);  // converts color to sRGB
+        color = tmp.getColor4f();
+    }
+    return sk_sp<BlurDrawLooper>(new BlurDrawLooper(color, blurSigma, offset));
+}
+
+}  // namespace android
diff --git a/libs/hwui/hwui/BlurDrawLooper.h b/libs/hwui/hwui/BlurDrawLooper.h
new file mode 100644
index 0000000..7e6786f
--- /dev/null
+++ b/libs/hwui/hwui/BlurDrawLooper.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_GRAPHICS_BLURDRAWLOOPER_H_
+#define ANDROID_GRAPHICS_BLURDRAWLOOPER_H_
+
+#include <SkPaint.h>
+#include <SkRefCnt.h>
+
+class SkColorSpace;
+
+namespace android {
+
+class BlurDrawLooper : public SkRefCnt {
+public:
+    static sk_sp<BlurDrawLooper> Make(SkColor4f, SkColorSpace*, float blurSigma, SkPoint offset);
+
+    ~BlurDrawLooper() override;
+
+    // proc(SkPoint offset, const SkPaint& modifiedPaint)
+    template <typename DrawProc>
+    void apply(const SkPaint& paint, DrawProc proc) const {
+        SkPaint p(paint);
+        proc(this->apply(&p), p);  // draw the shadow
+        proc({0, 0}, paint);       // draw the original (on top)
+    }
+
+private:
+    const SkColor4f mColor;
+    const float mBlurSigma;
+    const SkPoint mOffset;
+
+    SkPoint apply(SkPaint* paint) const;
+
+    BlurDrawLooper(SkColor4f, float, SkPoint);
+};
+
+}  // namespace android
+
+#endif  // ANDROID_GRAPHICS_BLURDRAWLOOPER_H_
diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h
index 05bae5c..d9c9eee 100644
--- a/libs/hwui/hwui/Paint.h
+++ b/libs/hwui/hwui/Paint.h
@@ -17,11 +17,11 @@
 #ifndef ANDROID_GRAPHICS_PAINT_H_
 #define ANDROID_GRAPHICS_PAINT_H_
 
+#include "BlurDrawLooper.h"
 #include "Typeface.h"
 
 #include <cutils/compiler.h>
 
-#include <SkDrawLooper.h>
 #include <SkFont.h>
 #include <SkPaint.h>
 #include <string>
@@ -59,8 +59,8 @@
     SkFont& getSkFont() { return mFont; }
     const SkFont& getSkFont() const { return mFont; }
 
-    SkDrawLooper* getLooper() const { return mLooper.get(); }
-    void setLooper(sk_sp<SkDrawLooper> looper) { mLooper = std::move(looper); }
+    BlurDrawLooper* getLooper() const { return mLooper.get(); }
+    void setLooper(sk_sp<BlurDrawLooper> looper) { mLooper = std::move(looper); }
 
     // These shadow the methods on SkPaint, but we need to so we can keep related
     // attributes in-sync.
@@ -155,7 +155,7 @@
 
 private:
     SkFont mFont;
-    sk_sp<SkDrawLooper> mLooper;
+    sk_sp<BlurDrawLooper> mLooper;
 
     float mLetterSpacing = 0;
     float mWordSpacing = 0;
diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp
index 3c86b28..bcec0fa 100644
--- a/libs/hwui/jni/Paint.cpp
+++ b/libs/hwui/jni/Paint.cpp
@@ -25,7 +25,6 @@
 #include <nativehelper/ScopedUtfChars.h>
 #include <nativehelper/ScopedPrimitiveArray.h>
 
-#include "SkBlurDrawLooper.h"
 #include "SkColorFilter.h"
 #include "SkFont.h"
 #include "SkFontMetrics.h"
@@ -39,6 +38,7 @@
 #include "unicode/ushape.h"
 #include "utils/Blur.h"
 
+#include <hwui/BlurDrawLooper.h>
 #include <hwui/MinikinSkia.h>
 #include <hwui/MinikinUtils.h>
 #include <hwui/Paint.h>
@@ -964,13 +964,13 @@
         }
         else {
             SkScalar sigma = android::uirenderer::Blur::convertRadiusToSigma(radius);
-            paint->setLooper(SkBlurDrawLooper::Make(color, cs.get(), sigma, dx, dy));
+            paint->setLooper(BlurDrawLooper::Make(color, cs.get(), sigma, {dx, dy}));
         }
     }
 
     static jboolean hasShadowLayer(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
         Paint* paint = reinterpret_cast<Paint*>(paintHandle);
-        return paint->getLooper() && paint->getLooper()->asABlurShadow(nullptr);
+        return paint->getLooper() != nullptr;
     }
 
     static jboolean equalsForTextMeasurement(CRITICAL_JNI_PARAMS_COMMA jlong lPaint, jlong rPaint) {
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index ee7c4d8..b288402 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -194,28 +194,20 @@
     return filterPaint(std::move(paint));
 }
 
-static SkDrawLooper* get_looper(const Paint* paint) {
+static BlurDrawLooper* get_looper(const Paint* paint) {
     return paint ? paint->getLooper() : nullptr;
 }
 
 template <typename Proc>
-void applyLooper(SkDrawLooper* looper, const SkPaint* paint, Proc proc) {
+void applyLooper(BlurDrawLooper* looper, const SkPaint* paint, Proc proc) {
     if (looper) {
-        SkSTArenaAlloc<256> alloc;
-        SkDrawLooper::Context* ctx = looper->makeContext(&alloc);
-        if (ctx) {
-            SkDrawLooper::Context::Info info;
-            for (;;) {
-                SkPaint p;
-                if (paint) {
-                    p = *paint;
-                }
-                if (!ctx->next(&info, &p)) {
-                    break;
-                }
-                proc(info.fTranslate.fX, info.fTranslate.fY, &p);
-            }
+        SkPaint p;
+        if (paint) {
+            p = *paint;
         }
+        looper->apply(p, [&](SkPoint offset, const SkPaint& modifiedPaint) {
+            proc(offset.fX, offset.fY, &modifiedPaint);
+        });
     } else {
         proc(0, 0, paint);
     }
diff --git a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
index 7951537..a1ba70a 100644
--- a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
+++ b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
@@ -16,7 +16,6 @@
 
 #include "tests/common/TestUtils.h"
 
-#include <SkBlurDrawLooper.h>
 #include <SkColorMatrixFilter.h>
 #include <SkColorSpace.h>
 #include <SkImagePriv.h>
@@ -85,15 +84,3 @@
     ASSERT_EQ(sRGB1.get(), sRGB2.get());
 }
 
-TEST(SkiaBehavior, blurDrawLooper) {
-    sk_sp<SkDrawLooper> looper = SkBlurDrawLooper::Make(SK_ColorRED, 5.0f, 3.0f, 4.0f);
-
-    SkDrawLooper::BlurShadowRec blur;
-    bool success = looper->asABlurShadow(&blur);
-    ASSERT_TRUE(success);
-
-    ASSERT_EQ(SK_ColorRED, blur.fColor);
-    ASSERT_EQ(5.0f, blur.fSigma);
-    ASSERT_EQ(3.0f, blur.fOffset.fX);
-    ASSERT_EQ(4.0f, blur.fOffset.fY);
-}
diff --git a/libs/hwui/tests/unit/SkiaCanvasTests.cpp b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
index f77ca2a..dae3c94 100644
--- a/libs/hwui/tests/unit/SkiaCanvasTests.cpp
+++ b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
@@ -17,7 +17,6 @@
 #include "tests/common/TestUtils.h"
 
 #include <hwui/Paint.h>
-#include <SkBlurDrawLooper.h>
 #include <SkCanvasStateUtils.h>
 #include <SkPicture.h>
 #include <SkPictureRecorder.h>
@@ -37,7 +36,7 @@
     // it is transparent to ensure that we still draw the rect since it has a looper
     paint.setColor(SK_ColorTRANSPARENT);
     // this is how view's shadow layers are implemented
-    paint.setLooper(SkBlurDrawLooper::Make(0xF0000000, 6.0f, 0, 10));
+    paint.setLooper(BlurDrawLooper::Make({0, 0, 0, 240.0f / 255}, nullptr, 6.0f, {0, 10}));
     canvas.drawRect(3, 3, 7, 7, paint);
 
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorWHITE);
diff --git a/libs/hwui/utils/PaintUtils.h b/libs/hwui/utils/PaintUtils.h
index e2fdf2f..09c6a4f 100644
--- a/libs/hwui/utils/PaintUtils.h
+++ b/libs/hwui/utils/PaintUtils.h
@@ -20,7 +20,6 @@
 #include <utils/Blur.h>
 
 #include <SkColorFilter.h>
-#include <SkDrawLooper.h>
 #include <SkPaint.h>
 #include <SkShader.h>
 
diff --git a/media/OWNERS b/media/OWNERS
index e741490..abfc8bf 100644
--- a/media/OWNERS
+++ b/media/OWNERS
@@ -26,3 +26,7 @@
 
 # SEO
 sungsoo@google.com
+
+# SEA/KIR/BVE
+jtinker@google.com
+robertshih@google.com
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 49f9d66..adb8a54c 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -38,6 +38,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
 import java.nio.ByteBuffer;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.LinkedHashSet;
@@ -2493,4 +2494,80 @@
             return mPlaybackId;
         }
     }
+
+    /**
+     * Returns recent {@link LogMessage LogMessages} associated with this {@link MediaDrm}
+     * instance.
+     */
+    @NonNull
+    public native List<LogMessage> getLogMessages();
+
+    /**
+     * A {@link LogMessage} records an event in the {@link MediaDrm} framework
+     * or vendor plugin.
+     */
+    public static class LogMessage {
+
+        /**
+         * Timing of the recorded event measured in milliseconds since the Epoch,
+         * 1970-01-01 00:00:00 +0000 (UTC).
+         */
+        public final long timestampMillis;
+
+        /**
+         * Priority of the recorded event.
+         * <p>
+         * Possible priority constants are defined in {@link Log}, e.g.:
+         * <ul>
+         *     <li>{@link Log#ASSERT}</li>
+         *     <li>{@link Log#ERROR}</li>
+         *     <li>{@link Log#WARN}</li>
+         *     <li>{@link Log#INFO}</li>
+         *     <li>{@link Log#DEBUG}</li>
+         *     <li>{@link Log#VERBOSE}</li>
+         * </ul>
+         */
+        @Log.Level
+        public final int priority;
+
+        /**
+         * Description of the recorded event.
+         */
+        @NonNull
+        public final String message;
+
+        private LogMessage(long timestampMillis, int priority, String message) {
+            this.timestampMillis = timestampMillis;
+            if (priority < Log.VERBOSE || priority > Log.ASSERT) {
+                throw new IllegalArgumentException("invalid log priority " + priority);
+            }
+            this.priority = priority;
+            this.message = message;
+        }
+
+        private char logPriorityChar() {
+            switch (priority) {
+                case Log.VERBOSE:
+                    return 'V';
+                case Log.DEBUG:
+                    return 'D';
+                case Log.INFO:
+                    return 'I';
+                case Log.WARN:
+                    return 'W';
+                case Log.ERROR:
+                    return 'E';
+                case Log.ASSERT:
+                    return 'F';
+                default:
+            }
+            return 'U';
+        }
+
+        @Override
+        public String toString() {
+            return String.format("LogMessage{%s %c %s}",
+                    Instant.ofEpochMilli(timestampMillis), logPriorityChar(), message);
+        }
+    }
 }
diff --git a/media/java/android/media/metrics/NetworkEvent.java b/media/java/android/media/metrics/NetworkEvent.java
index a330bc0..029edeb 100644
--- a/media/java/android/media/metrics/NetworkEvent.java
+++ b/media/java/android/media/metrics/NetworkEvent.java
@@ -17,6 +17,7 @@
 package android.media.metrics;
 
 import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Parcel;
@@ -27,22 +28,30 @@
 import java.util.Objects;
 
 /**
- * Playback network event.
- * @hide
+ * Media network event.
  */
-public final class NetworkEvent implements Parcelable {
+public final class NetworkEvent extends Event implements Parcelable {
+    /** Network type is not specified. Default type. */
     public static final int NETWORK_TYPE_NONE = 0;
+    /** Other network type */
     public static final int NETWORK_TYPE_OTHER = 1;
+    /** Wi-Fi network */
     public static final int NETWORK_TYPE_WIFI = 2;
+    /** Ethernet network */
     public static final int NETWORK_TYPE_ETHERNET = 3;
+    /** 2G network */
     public static final int NETWORK_TYPE_2G = 4;
+    /** 3G network */
     public static final int NETWORK_TYPE_3G = 5;
+    /** 4G network */
     public static final int NETWORK_TYPE_4G = 6;
+    /** 5G NSA network */
     public static final int NETWORK_TYPE_5G_NSA = 7;
+    /** 5G SA network */
     public static final int NETWORK_TYPE_5G_SA = 8;
 
-    private final int mType;
-    private final long mTimeSincePlaybackCreatedMillis;
+    private final int mNetworkType;
+    private final long mTimeSinceCreatedMillis;
 
     /** @hide */
     @IntDef(prefix = "NETWORK_TYPE_", value = {
@@ -61,6 +70,7 @@
 
     /**
      * Network type to string.
+     * @hide
      */
     public static String networkTypeToString(@NetworkType int value) {
         switch (value) {
@@ -92,25 +102,34 @@
      *
      * @hide
      */
-    public NetworkEvent(@NetworkType int type, long timeSincePlaybackCreatedMillis) {
-        this.mType = type;
-        this.mTimeSincePlaybackCreatedMillis = timeSincePlaybackCreatedMillis;
+    public NetworkEvent(@NetworkType int type, long timeSinceCreatedMillis) {
+        this.mNetworkType = type;
+        this.mTimeSinceCreatedMillis = timeSinceCreatedMillis;
     }
 
+    /**
+     * Gets network type.
+     */
     @NetworkType
-    public int getType() {
-        return mType;
+    public int getNetworkType() {
+        return mNetworkType;
     }
 
-    public long getTimeSincePlaybackCreatedMillis() {
-        return mTimeSincePlaybackCreatedMillis;
+    /**
+     * Gets timestamp since the creation in milliseconds.
+     * @return the timestamp since the creation in milliseconds, or -1 if unknown.
+     */
+    @Override
+    @IntRange(from = -1)
+    public long getTimeSinceCreatedMillis() {
+        return mTimeSinceCreatedMillis;
     }
 
     @Override
     public String toString() {
         return "NetworkEvent { "
-                + "type = " + mType + ", "
-                + "timeSincePlaybackCreatedMillis = " + mTimeSincePlaybackCreatedMillis
+                + "networkType = " + mNetworkType + ", "
+                + "timeSinceCreatedMillis = " + mTimeSinceCreatedMillis
                 + " }";
     }
 
@@ -119,19 +138,19 @@
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
         NetworkEvent that = (NetworkEvent) o;
-        return mType == that.mType
-                && mTimeSincePlaybackCreatedMillis == that.mTimeSincePlaybackCreatedMillis;
+        return mNetworkType == that.mNetworkType
+                && mTimeSinceCreatedMillis == that.mTimeSinceCreatedMillis;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mType, mTimeSincePlaybackCreatedMillis);
+        return Objects.hash(mNetworkType, mTimeSinceCreatedMillis);
     }
 
     @Override
     public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
-        dest.writeInt(mType);
-        dest.writeLong(mTimeSincePlaybackCreatedMillis);
+        dest.writeInt(mNetworkType);
+        dest.writeLong(mTimeSinceCreatedMillis);
     }
 
     @Override
@@ -142,12 +161,15 @@
     /** @hide */
     /* package-private */ NetworkEvent(@NonNull android.os.Parcel in) {
         int type = in.readInt();
-        long timeSincePlaybackCreatedMillis = in.readLong();
+        long timeSinceCreatedMillis = in.readLong();
 
-        this.mType = type;
-        this.mTimeSincePlaybackCreatedMillis = timeSincePlaybackCreatedMillis;
+        this.mNetworkType = type;
+        this.mTimeSinceCreatedMillis = timeSinceCreatedMillis;
     }
 
+    /**
+     * Used to read a NetworkEvent from a Parcel.
+     */
     public static final @NonNull Parcelable.Creator<NetworkEvent> CREATOR =
             new Parcelable.Creator<NetworkEvent>() {
         @Override
@@ -165,13 +187,11 @@
      * A builder for {@link NetworkEvent}
      */
     public static final class Builder {
-        private int mType;
-        private long mTimeSincePlaybackCreatedMillis;
+        private int mNetworkType = NETWORK_TYPE_NONE;
+        private long mTimeSinceCreatedMillis = -1;
 
         /**
          * Creates a new Builder.
-         *
-         * @hide
          */
         public Builder() {
         }
@@ -179,24 +199,24 @@
         /**
          * Sets network type.
          */
-        public @NonNull Builder setType(@NetworkType int value) {
-            mType = value;
+        public @NonNull Builder setNetworkType(@NetworkType int value) {
+            mNetworkType = value;
             return this;
         }
 
         /**
          * Sets timestamp since the creation in milliseconds.
+         * @param value the timestamp since the creation in milliseconds.
+         *              -1 indicates the value is unknown.
          */
-        public @NonNull Builder setTimeSincePlaybackCreatedMillis(long value) {
-            mTimeSincePlaybackCreatedMillis = value;
+        public @NonNull Builder setTimeSinceCreatedMillis(@IntRange(from = -1) long value) {
+            mTimeSinceCreatedMillis = value;
             return this;
         }
 
         /** Builds the instance. */
         public @NonNull NetworkEvent build() {
-            NetworkEvent o = new NetworkEvent(
-                    mType,
-                    mTimeSincePlaybackCreatedMillis);
+            NetworkEvent o = new NetworkEvent(mNetworkType, mTimeSinceCreatedMillis);
             return o;
         }
     }
diff --git a/media/java/android/media/metrics/PlaybackComponent.java b/media/java/android/media/metrics/PlaybackComponent.java
index 94e55b4..1cadf3b 100644
--- a/media/java/android/media/metrics/PlaybackComponent.java
+++ b/media/java/android/media/metrics/PlaybackComponent.java
@@ -17,13 +17,10 @@
 package android.media.metrics;
 
 import android.annotation.NonNull;
-import android.annotation.TestApi;
 
 /**
  * Interface for playback related components used by playback metrics.
- * @hide
  */
-@TestApi
 public interface PlaybackComponent {
 
     /**
diff --git a/media/java/android/media/metrics/PlaybackMetrics.java b/media/java/android/media/metrics/PlaybackMetrics.java
index 070b4e4..4aa61662 100644
--- a/media/java/android/media/metrics/PlaybackMetrics.java
+++ b/media/java/android/media/metrics/PlaybackMetrics.java
@@ -17,6 +17,7 @@
 package android.media.metrics;
 
 import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Parcel;
@@ -33,35 +34,57 @@
 
 /**
  * This class is used to store playback data.
- * @hide
  */
 public final class PlaybackMetrics implements Parcelable {
-    // TODO(b/177209128): JavaDoc for the constants.
+    /** Unknown stream source. */
     public static final int STREAM_SOURCE_UNKNOWN = 0;
+    /** Stream from network. */
     public static final int STREAM_SOURCE_NETWORK = 1;
+    /** Stream from device. */
     public static final int STREAM_SOURCE_DEVICE = 2;
+    /** Stream from more than one sources. */
     public static final int STREAM_SOURCE_MIXED = 3;
 
+    /** Unknown stream type. */
     public static final int STREAM_TYPE_UNKNOWN = 0;
+    /** Other stream type. */
     public static final int STREAM_TYPE_OTHER = 1;
+    /** Progressive stream type. */
     public static final int STREAM_TYPE_PROGRESSIVE = 2;
+    /** DASH (Dynamic Adaptive Streaming over HTTP) stream type. */
     public static final int STREAM_TYPE_DASH = 3;
+    /** HLS (HTTP Live Streaming) stream type. */
     public static final int STREAM_TYPE_HLS = 4;
+    /** SS (HTTP Smooth Streaming) stream type. */
     public static final int STREAM_TYPE_SS = 5;
 
+    /** VOD (Video on Demand) playback type. */
     public static final int PLAYBACK_TYPE_VOD = 0;
+    /** Live playback type. */
     public static final int PLAYBACK_TYPE_LIVE = 1;
+    /** Other playback type. */
     public static final int PLAYBACK_TYPE_OTHER = 2;
 
+    /** DRM is not used. */
     public static final int DRM_TYPE_NONE = 0;
+    /** Other DRM type. */
     public static final int DRM_TYPE_OTHER = 1;
+    /** Play ready DRM type. */
     public static final int DRM_TYPE_PLAY_READY = 2;
+    /** Widevine L1 DRM type. */
     public static final int DRM_TYPE_WIDEVINE_L1 = 3;
+    /** Widevine L3 DRM type. */
     public static final int DRM_TYPE_WIDEVINE_L3 = 4;
-    // TODO: add DRM_TYPE_CLEARKEY
+    /** Widevine L3 fallback DRM type. */
+    public static final int DRM_TYPE_WV_L3_FALLBACK = 5;
+    /** Clear key DRM type. */
+    public static final int DRM_TYPE_CLEARKEY = 6;
 
+    /** Main contents. */
     public static final int CONTENT_TYPE_MAIN = 0;
+    /** Advertisement contents. */
     public static final int CONTENT_TYPE_AD = 1;
+    /** Other contents. */
     public static final int CONTENT_TYPE_OTHER = 2;
 
 
@@ -102,7 +125,9 @@
         DRM_TYPE_OTHER,
         DRM_TYPE_PLAY_READY,
         DRM_TYPE_WIDEVINE_L1,
-        DRM_TYPE_WIDEVINE_L3
+        DRM_TYPE_WIDEVINE_L3,
+        DRM_TYPE_WV_L3_FALLBACK,
+        DRM_TYPE_CLEARKEY
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface DrmType {}
@@ -173,6 +198,11 @@
         this.mNetworkTransferDurationMillis = networkTransferDurationMillis;
     }
 
+    /**
+     * Gets the media duration in milliseconds.
+     * @return the media duration in milliseconds, or -1 if unknown.
+     */
+    @IntRange(from = -1)
     public long getMediaDurationMillis() {
         return mMediaDurationMillis;
     }
@@ -241,28 +271,36 @@
 
     /**
      * Gets video frames played.
+     * @return the video frames played, or -1 if unknown.
      */
+    @IntRange(from = -1, to = Integer.MAX_VALUE)
     public int getVideoFramesPlayed() {
         return mVideoFramesPlayed;
     }
 
     /**
      * Gets video frames dropped.
+     * @return the video frames dropped, or -1 if unknown.
      */
+    @IntRange(from = -1, to = Integer.MAX_VALUE)
     public int getVideoFramesDropped() {
         return mVideoFramesDropped;
     }
 
     /**
      * Gets audio underrun count.
+     * @return the audio underrun count, or -1 if unknown.
      */
+    @IntRange(from = -1, to = Integer.MAX_VALUE)
     public int getAudioUnderrunCount() {
         return mAudioUnderrunCount;
     }
 
     /**
      * Gets number of network bytes read.
+     * @return the number of network bytes read, or -1 if unknown.
      */
+    @IntRange(from = -1)
     public long getNetworkBytesRead() {
         return mNetworkBytesRead;
     }
@@ -270,6 +308,7 @@
     /**
      * Gets number of local bytes read.
      */
+    @IntRange(from = -1)
     public long getLocalBytesRead() {
         return mLocalBytesRead;
     }
@@ -277,6 +316,7 @@
     /**
      * Gets network transfer duration in milliseconds.
      */
+    @IntRange(from = -1)
     public long getNetworkTransferDurationMillis() {
         return mNetworkTransferDurationMillis;
     }
@@ -415,34 +455,33 @@
      */
     public static final class Builder {
 
-        private long mMediaDurationMillis;
-        private int mStreamSource;
-        private int mStreamType;
-        private int mPlaybackType;
-        private int mDrmType;
-        private int mContentType;
+        private long mMediaDurationMillis = -1;
+        private int mStreamSource = STREAM_SOURCE_UNKNOWN;
+        private int mStreamType = STREAM_TYPE_UNKNOWN;
+        private int mPlaybackType = PLAYBACK_TYPE_OTHER;
+        private int mDrmType = DRM_TYPE_NONE;
+        private int mContentType = CONTENT_TYPE_OTHER;
         private @Nullable String mPlayerName;
         private @Nullable String mPlayerVersion;
         private @NonNull List<Long> mExperimentIds = new ArrayList<>();
-        private int mVideoFramesPlayed;
-        private int mVideoFramesDropped;
-        private int mAudioUnderrunCount;
-        private long mNetworkBytesRead;
-        private long mLocalBytesRead;
-        private long mNetworkTransferDurationMillis;
+        private int mVideoFramesPlayed = -1;
+        private int mVideoFramesDropped = -1;
+        private int mAudioUnderrunCount = -1;
+        private long mNetworkBytesRead = -1;
+        private long mLocalBytesRead = -1;
+        private long mNetworkTransferDurationMillis = -1;
 
         /**
          * Creates a new Builder.
-         *
-         * @hide
          */
         public Builder() {
         }
 
         /**
          * Sets the media duration in milliseconds.
+         * @param value the media duration in milliseconds. -1 indicates the value is unknown.
          */
-        public @NonNull Builder setMediaDurationMillis(long value) {
+        public @NonNull Builder setMediaDurationMillis(@IntRange(from = -1) long value) {
             mMediaDurationMillis = value;
             return this;
         }
@@ -474,7 +513,7 @@
         /**
          * Sets the DRM type.
          */
-        public @NonNull Builder setDrmType(@StreamType int value) {
+        public @NonNull Builder setDrmType(@DrmType int value) {
             mDrmType = value;
             return this;
         }
@@ -513,48 +552,58 @@
 
         /**
          * Sets the video frames played.
+         * @param value the video frames played. -1 indicates the value is unknown.
          */
-        public @NonNull Builder setVideoFramesPlayed(int value) {
+        public @NonNull Builder setVideoFramesPlayed(
+                @IntRange(from = -1, to = Integer.MAX_VALUE) int value) {
             mVideoFramesPlayed = value;
             return this;
         }
 
         /**
          * Sets the video frames dropped.
+         * @param value the video frames dropped. -1 indicates the value is unknown.
          */
-        public @NonNull Builder setVideoFramesDropped(int value) {
+        public @NonNull Builder setVideoFramesDropped(
+                @IntRange(from = -1, to = Integer.MAX_VALUE) int value) {
             mVideoFramesDropped = value;
             return this;
         }
 
         /**
          * Sets the audio underrun count.
+         * @param value the audio underrun count. -1 indicates the value is unknown.
          */
-        public @NonNull Builder setAudioUnderrunCount(int value) {
+        public @NonNull Builder setAudioUnderrunCount(
+                @IntRange(from = -1, to = Integer.MAX_VALUE) int value) {
             mAudioUnderrunCount = value;
             return this;
         }
 
         /**
          * Sets the number of network bytes read.
+         * @param value the number of network bytes read. -1 indicates the value is unknown.
          */
-        public @NonNull Builder setNetworkBytesRead(long value) {
+        public @NonNull Builder setNetworkBytesRead(@IntRange(from = -1) long value) {
             mNetworkBytesRead = value;
             return this;
         }
 
         /**
          * Sets the number of local bytes read.
+         * @param value the number of local bytes read. -1 indicates the value is unknown.
          */
-        public @NonNull Builder setLocalBytesRead(long value) {
+        public @NonNull Builder setLocalBytesRead(@IntRange(from = -1) long value) {
             mLocalBytesRead = value;
             return this;
         }
 
         /**
          * Sets the network transfer duration in milliseconds.
+         * @param value the network transfer duration in milliseconds.
+         *              -1 indicates the value is unknown.
          */
-        public @NonNull Builder setNetworkTransferDurationMillis(long value) {
+        public @NonNull Builder setNetworkTransferDurationMillis(@IntRange(from = -1) long value) {
             mNetworkTransferDurationMillis = value;
             return this;
         }
diff --git a/media/java/android/media/metrics/PlaybackSession.java b/media/java/android/media/metrics/PlaybackSession.java
index 4cb957f..4ee8a45 100644
--- a/media/java/android/media/metrics/PlaybackSession.java
+++ b/media/java/android/media/metrics/PlaybackSession.java
@@ -45,7 +45,6 @@
 
     /**
      * Reports playback metrics.
-     * @hide
      */
     public void reportPlaybackMetrics(@NonNull PlaybackMetrics metrics) {
         mManager.reportPlaybackMetrics(mId, metrics);
@@ -60,9 +59,8 @@
 
     /**
      * Reports network event.
-     * @hide
      */
-    public void reportNetworkEvent(NetworkEvent event) {
+    public void reportNetworkEvent(@NonNull NetworkEvent event) {
         mManager.reportNetworkEvent(mId, event);
     }
 
@@ -75,9 +73,8 @@
 
     /**
      * Reports track change event.
-     * @hide
      */
-    public void reportTrackChangeEvent(TrackChangeEvent event) {
+    public void reportTrackChangeEvent(@NonNull TrackChangeEvent event) {
         mManager.reportTrackChangeEvent(mId, event);
     }
 
diff --git a/media/java/android/media/metrics/TrackChangeEvent.java b/media/java/android/media/metrics/TrackChangeEvent.java
index fff0e36..ef25357 100644
--- a/media/java/android/media/metrics/TrackChangeEvent.java
+++ b/media/java/android/media/metrics/TrackChangeEvent.java
@@ -17,6 +17,7 @@
 package android.media.metrics;
 
 import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Parcel;
@@ -28,20 +29,29 @@
 
 /**
  * Playback track change event.
- * @hide
  */
-public final class TrackChangeEvent implements Parcelable {
+public final class TrackChangeEvent extends Event implements Parcelable {
+    /** The track is off. */
     public static final int TRACK_STATE_OFF = 0;
+    /** The track is on. */
     public static final int TRACK_STATE_ON = 1;
 
+    /** Unknown track change reason. */
     public static final int TRACK_CHANGE_REASON_UNKNOWN = 0;
+    /** Other track change reason. */
     public static final int TRACK_CHANGE_REASON_OTHER = 1;
+    /** Track change reason for initial state. */
     public static final int TRACK_CHANGE_REASON_INITIAL = 2;
+    /** Track change reason for manual changes. */
     public static final int TRACK_CHANGE_REASON_MANUAL = 3;
+    /** Track change reason for adaptive changes. */
     public static final int TRACK_CHANGE_REASON_ADAPTIVE = 4;
 
+    /** Audio track. */
     public static final int TRACK_TYPE_AUDIO = 0;
+    /** Video track. */
     public static final int TRACK_TYPE_VIDEO = 1;
+    /** Text track. */
     public static final int TRACK_TYPE_TEXT = 2;
 
     private final int mState;
@@ -50,7 +60,7 @@
     private final @Nullable String mSampleMimeType;
     private final @Nullable String mCodecName;
     private final int mBitrate;
-    private final long mTimeSincePlaybackCreatedMillis;
+    private final long mTimeSinceCreatedMillis;
     private final int mType;
     private final @Nullable String mLanguage;
     private final @Nullable String mLanguageRegion;
@@ -96,7 +106,7 @@
             @Nullable String sampleMimeType,
             @Nullable String codecName,
             int bitrate,
-            long timeSincePlaybackCreatedMillis,
+            long timeSinceCreatedMillis,
             int type,
             @Nullable String language,
             @Nullable String languageRegion,
@@ -110,7 +120,7 @@
         this.mSampleMimeType = sampleMimeType;
         this.mCodecName = codecName;
         this.mBitrate = bitrate;
-        this.mTimeSincePlaybackCreatedMillis = timeSincePlaybackCreatedMillis;
+        this.mTimeSinceCreatedMillis = timeSinceCreatedMillis;
         this.mType = type;
         this.mLanguage = language;
         this.mLanguageRegion = languageRegion;
@@ -120,34 +130,60 @@
         this.mHeight = height;
     }
 
+    /**
+     * Gets track state.
+     */
     @TrackState
     public int getTrackState() {
         return mState;
     }
 
+    /**
+     * Gets track change reason.
+     */
     @TrackChangeReason
     public int getTrackChangeReason() {
         return mReason;
     }
 
+    /**
+     * Gets container MIME type.
+     */
     public @Nullable String getContainerMimeType() {
         return mContainerMimeType;
     }
 
+    /**
+     * Gets the MIME type of the video/audio/text samples.
+     */
     public @Nullable String getSampleMimeType() {
         return mSampleMimeType;
     }
 
+    /**
+     * Gets codec name.
+     */
     public @Nullable String getCodecName() {
         return mCodecName;
     }
 
+    /**
+     * Gets bitrate.
+     * @return the bitrate, or -1 if unknown.
+     */
+    @IntRange(from = -1, to = Integer.MAX_VALUE)
     public int getBitrate() {
         return mBitrate;
     }
 
-    public long getTimeSincePlaybackCreatedMillis() {
-        return mTimeSincePlaybackCreatedMillis;
+    /**
+     * Gets timestamp since the creation in milliseconds.
+     * @return the timestamp since the creation in milliseconds, or -1 if unknown.
+     */
+    @Override
+    @IntRange(from = -1)
+    public long getTimeSinceCreatedMillis() {
+        return mTimeSinceCreatedMillis;
     }
 
     @TrackType
@@ -155,26 +191,55 @@
         return mType;
     }
 
+    /**
+     * Gets language code.
+     * @return a two-letter ISO 639-1 language code.
+     */
     public @Nullable String getLanguage() {
         return mLanguage;
     }
 
+
+    /**
+     * Gets language region code.
+     * @return an IETF BCP 47 optional language region subtag based on a two-letter country code.
+     */
     public @Nullable String getLanguageRegion() {
         return mLanguageRegion;
     }
 
+    /**
+     * Gets channel count.
+     * @return the channel count, or -1 if unknown.
+     */
+    @IntRange(from = -1, to = Integer.MAX_VALUE)
     public int getChannelCount() {
         return mChannelCount;
     }
 
+    /**
+     * Gets sample rate.
+     * @return the sample rate, or -1 if unknown.
+     */
+    @IntRange(from = -1, to = Integer.MAX_VALUE)
     public int getSampleRate() {
         return mSampleRate;
     }
 
+    /**
+     * Gets video width.
+     * @return the video width, or -1 if unknown.
+     */
+    @IntRange(from = -1, to = Integer.MAX_VALUE)
     public int getWidth() {
         return mWidth;
     }
 
+    /**
+     * Gets video height.
+     * @return the video height, or -1 if unknown.
+     */
+    @IntRange(from = -1, to = Integer.MAX_VALUE)
     public int getHeight() {
         return mHeight;
     }
@@ -194,7 +259,7 @@
         if (mSampleMimeType != null) dest.writeString(mSampleMimeType);
         if (mCodecName != null) dest.writeString(mCodecName);
         dest.writeInt(mBitrate);
-        dest.writeLong(mTimeSincePlaybackCreatedMillis);
+        dest.writeLong(mTimeSinceCreatedMillis);
         dest.writeInt(mType);
         if (mLanguage != null) dest.writeString(mLanguage);
         if (mLanguageRegion != null) dest.writeString(mLanguageRegion);
@@ -218,7 +283,7 @@
         String sampleMimeType = (flg & 0x8) == 0 ? null : in.readString();
         String codecName = (flg & 0x10) == 0 ? null : in.readString();
         int bitrate = in.readInt();
-        long timeSincePlaybackCreatedMillis = in.readLong();
+        long timeSinceCreatedMillis = in.readLong();
         int type = in.readInt();
         String language = (flg & 0x100) == 0 ? null : in.readString();
         String languageRegion = (flg & 0x200) == 0 ? null : in.readString();
@@ -233,7 +298,7 @@
         this.mSampleMimeType = sampleMimeType;
         this.mCodecName = codecName;
         this.mBitrate = bitrate;
-        this.mTimeSincePlaybackCreatedMillis = timeSincePlaybackCreatedMillis;
+        this.mTimeSinceCreatedMillis = timeSinceCreatedMillis;
         this.mType = type;
         this.mLanguage = language;
         this.mLanguageRegion = languageRegion;
@@ -256,38 +321,24 @@
         }
     };
 
-
-
-    // Code below generated by codegen v1.0.22.
-    //
-    // DO NOT MODIFY!
-    // CHECKSTYLE:OFF Generated code
-    //
-    // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/media/java/android/media/metrics/TrackChangeEvent.java
-    //
-    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
-    //   Settings > Editor > Code Style > Formatter Control
-    //@formatter:off
-
     @Override
     public String toString() {
-        return "TrackChangeEvent { " +
-                "state = " + mState + ", " +
-                "reason = " + mReason + ", " +
-                "containerMimeType = " + mContainerMimeType + ", " +
-                "sampleMimeType = " + mSampleMimeType + ", " +
-                "codecName = " + mCodecName + ", " +
-                "bitrate = " + mBitrate + ", " +
-                "timeSincePlaybackCreatedMillis = " + mTimeSincePlaybackCreatedMillis + ", " +
-                "type = " + mType + ", " +
-                "language = " + mLanguage + ", " +
-                "languageRegion = " + mLanguageRegion + ", " +
-                "channelCount = " + mChannelCount + ", " +
-                "sampleRate = " + mSampleRate + ", " +
-                "width = " + mWidth + ", " +
-                "height = " + mHeight +
-        " }";
+        return "TrackChangeEvent { "
+                + "state = " + mState + ", "
+                + "reason = " + mReason + ", "
+                + "containerMimeType = " + mContainerMimeType + ", "
+                + "sampleMimeType = " + mSampleMimeType + ", "
+                + "codecName = " + mCodecName + ", "
+                + "bitrate = " + mBitrate + ", "
+                + "timeSinceCreatedMillis = " + mTimeSinceCreatedMillis + ", "
+                + "type = " + mType + ", "
+                + "language = " + mLanguage + ", "
+                + "languageRegion = " + mLanguageRegion + ", "
+                + "channelCount = " + mChannelCount + ", "
+                + "sampleRate = " + mSampleRate + ", "
+                + "width = " + mWidth + ", "
+                + "height = " + mHeight
+                + " }";
     }
 
     @Override
@@ -301,7 +352,7 @@
                 && Objects.equals(mSampleMimeType, that.mSampleMimeType)
                 && Objects.equals(mCodecName, that.mCodecName)
                 && mBitrate == that.mBitrate
-                && mTimeSincePlaybackCreatedMillis == that.mTimeSincePlaybackCreatedMillis
+                && mTimeSinceCreatedMillis == that.mTimeSinceCreatedMillis
                 && mType == that.mType
                 && Objects.equals(mLanguage, that.mLanguage)
                 && Objects.equals(mLanguageRegion, that.mLanguageRegion)
@@ -314,7 +365,7 @@
     @Override
     public int hashCode() {
         return Objects.hash(mState, mReason, mContainerMimeType, mSampleMimeType, mCodecName,
-                mBitrate, mTimeSincePlaybackCreatedMillis, mType, mLanguage, mLanguageRegion,
+                mBitrate, mTimeSinceCreatedMillis, mType, mLanguage, mLanguageRegion,
                 mChannelCount, mSampleRate, mWidth, mHeight);
     }
 
@@ -323,32 +374,33 @@
      */
     public static final class Builder {
         // TODO: check track type for the setters.
-        private int mState;
-        private int mReason;
+        private int mState = TRACK_STATE_OFF;
+        private int mReason = TRACK_CHANGE_REASON_UNKNOWN;
         private @Nullable String mContainerMimeType;
         private @Nullable String mSampleMimeType;
         private @Nullable String mCodecName;
-        private int mBitrate;
-        private long mTimeSincePlaybackCreatedMillis;
-        private int mType;
+        private int mBitrate = -1;
+        private long mTimeSinceCreatedMillis = -1;
+        private final int mType;
         private @Nullable String mLanguage;
         private @Nullable String mLanguageRegion;
-        private int mChannelCount;
-        private int mSampleRate;
-        private int mWidth;
-        private int mHeight;
+        private int mChannelCount = -1;
+        private int mSampleRate = -1;
+        private int mWidth = -1;
+        private int mHeight = -1;
 
         private long mBuilderFieldsSet = 0L;
 
         /**
          * Creates a new Builder.
-         *
-         * @hide
          */
         public Builder(int type) {
             mType = type;
         }
 
+        /**
+         * Sets track state.
+         */
         public @NonNull Builder setTrackState(@TrackState int value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x1;
@@ -356,6 +408,9 @@
             return this;
         }
 
+        /**
+         * Sets track change reason.
+         */
         public @NonNull Builder setTrackChangeReason(@TrackChangeReason int value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x2;
@@ -363,6 +418,9 @@
             return this;
         }
 
+        /**
+         * Sets container MIME type.
+         */
         public @NonNull Builder setContainerMimeType(@NonNull String value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x4;
@@ -370,6 +428,9 @@
             return this;
         }
 
+        /**
+         * Sets the MIME type of the video/audio/text samples.
+         */
         public @NonNull Builder setSampleMimeType(@NonNull String value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x8;
@@ -377,6 +438,9 @@
             return this;
         }
 
+        /**
+         * Sets codec name.
+         */
         public @NonNull Builder setCodecName(@NonNull String value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x10;
@@ -384,27 +448,33 @@
             return this;
         }
 
-        public @NonNull Builder setBitrate(int value) {
+        /**
+         * Sets bitrate in bits per second.
+         * @param value the bitrate in bits per second. -1 indicates the value is unknown.
+         */
+        public @NonNull Builder setBitrate(@IntRange(from = -1, to = Integer.MAX_VALUE) int value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x20;
             mBitrate = value;
             return this;
         }
 
-        public @NonNull Builder setTimeSincePlaybackCreatedMillis(long value) {
+        /**
+         * Sets timestamp since the creation in milliseconds.
+         * @param value the timestamp since the creation in milliseconds.
+         *              -1 indicates the value is unknown.
+         */
+        public @NonNull Builder setTimeSinceCreatedMillis(@IntRange(from = -1) long value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x40;
-            mTimeSincePlaybackCreatedMillis = value;
+            mTimeSinceCreatedMillis = value;
             return this;
         }
 
-        public @NonNull Builder setTrackType(@TrackType int value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x80;
-            mType = value;
-            return this;
-        }
-
+        /**
+         * Sets language code.
+         * @param value a two-letter ISO 639-1 language code.
+         */
         public @NonNull Builder setLanguage(@NonNull String value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x100;
@@ -412,6 +482,11 @@
             return this;
         }
 
+        /**
+         * Sets language region code.
+         * @param value an IETF BCP 47 optional language region subtag based on a two-letter country
+         *              code.
+         */
         public @NonNull Builder setLanguageRegion(@NonNull String value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x200;
@@ -419,28 +494,46 @@
             return this;
         }
 
-        public @NonNull Builder setChannelCount(int value) {
+        /**
+         * Sets channel count.
+         * @param value the channel count. -1 indicates the value is unknown.
+         */
+        public @NonNull Builder setChannelCount(
+                @IntRange(from = -1, to = Integer.MAX_VALUE) int value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x400;
             mChannelCount = value;
             return this;
         }
 
-        public @NonNull Builder setSampleRate(int value) {
+        /**
+         * Sets sample rate.
+         * @param value the sample rate. -1 indicates the value is unknown.
+         */
+        public @NonNull Builder setSampleRate(
+                @IntRange(from = -1, to = Integer.MAX_VALUE) int value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x800;
             mSampleRate = value;
             return this;
         }
 
-        public @NonNull Builder setWidth(int value) {
+        /**
+         * Sets video width.
+         * @param value the video width. -1 indicates the value is unknown.
+         */
+        public @NonNull Builder setWidth(@IntRange(from = -1, to = Integer.MAX_VALUE) int value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x1000;
             mWidth = value;
             return this;
         }
 
-        public @NonNull Builder setHeight(int value) {
+        /**
+         * Sets video height.
+         * @param value the video height. -1 indicates the value is unknown.
+         */
+        public @NonNull Builder setHeight(@IntRange(from = -1, to = Integer.MAX_VALUE) int value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x2000;
             mHeight = value;
@@ -459,7 +552,7 @@
                     mSampleMimeType,
                     mCodecName,
                     mBitrate,
-                    mTimeSincePlaybackCreatedMillis,
+                    mTimeSinceCreatedMillis,
                     mType,
                     mLanguage,
                     mLanguageRegion,
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 740bc2d..c0185dc 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -2525,7 +2525,9 @@
         /**
          * Pauses TV program recording in the current recording session.
          *
-         * @param params A set of extra parameters which might be handled with this event.
+         * @param params Domain-specific data for this request. Keys <em>must</em> be a scoped
+         *            name, i.e. prefixed with a package name you own, so that different developers
+         *            will not create conflicting keys.
          *        {@link TvRecordingClient#pauseRecording(Bundle)}.
          */
         void pauseRecording(@NonNull Bundle params) {
@@ -2543,7 +2545,9 @@
         /**
          * Resumes TV program recording in the current recording session.
          *
-         * @param params A set of extra parameters which might be handled with this event.
+         * @param params Domain-specific data for this request. Keys <em>must</em> be a scoped
+         *            name, i.e. prefixed with a package name you own, so that different developers
+         *            will not create conflicting keys.
          *        {@link TvRecordingClient#resumeRecording(Bundle)}.
          */
         void resumeRecording(@NonNull Bundle params) {
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 4972529..6160b81 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -64,6 +64,7 @@
         "android.hardware.cas@1.0",
         "android.hardware.cas.native@1.0",
         "android.hardware.drm@1.3",
+        "android.hardware.drm@1.4",
         "android.hidl.memory@1.0",
         "android.hidl.token@1.0-utils",
     ],
diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
index 0e8719e..22c3572 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -37,6 +37,7 @@
 #include <mediadrm/DrmUtils.h>
 #include <mediadrm/IDrmMetricsConsumer.h>
 #include <mediadrm/IDrm.h>
+#include <utils/Vector.h>
 
 using ::android::os::PersistableBundle;
 namespace drm = ::android::hardware::drm;
@@ -187,6 +188,11 @@
     jclass classId;
 };
 
+struct LogMessageFields {
+    jmethodID init;
+    jclass classId;
+};
+
 struct fields_t {
     jfieldID context;
     jmethodID post_event;
@@ -208,6 +214,7 @@
     jmethodID createFromParcelId;
     jclass parcelCreatorClassId;
     KeyStatusFields keyStatus;
+    LogMessageFields logMessage;
 };
 
 static fields_t gFields;
@@ -224,6 +231,19 @@
     return result;
 }
 
+jobject hidlLogMessagesToJavaList(JNIEnv *env, const Vector<drm::V1_4::LogMessage> &logs) {
+    jclass clazz = gFields.arraylistClassId;
+    jobject arrayList = env->NewObject(clazz, gFields.arraylist.init);
+    clazz = gFields.logMessage.classId;
+    for (auto log: logs) {
+        jobject jLog = env->NewObject(clazz, gFields.logMessage.init,
+                static_cast<jlong>(log.timeMs),
+                static_cast<jint>(log.priority),
+                env->NewStringUTF(log.message.c_str()));
+        env->CallBooleanMethod(arrayList, gFields.arraylist.add, jLog);
+    }
+    return arrayList;
+}
 }  // namespace anonymous
 
 // ----------------------------------------------------------------------------
@@ -907,6 +927,10 @@
     FIND_CLASS(clazz, "android/media/MediaDrm$KeyStatus");
     gFields.keyStatus.classId = static_cast<jclass>(env->NewGlobalRef(clazz));
     GET_METHOD_ID(gFields.keyStatus.init, clazz, "<init>", "([BI)V");
+
+    FIND_CLASS(clazz, "android/media/MediaDrm$LogMessage");
+    gFields.logMessage.classId = static_cast<jclass>(env->NewGlobalRef(clazz));
+    GET_METHOD_ID(gFields.logMessage.init, clazz, "<init>", "(JILjava/lang/String;)V");
 }
 
 static void android_media_MediaDrm_native_setup(
@@ -1996,6 +2020,22 @@
     throwExceptionAsNecessary(env, err, "Failed to set playbackId");
 }
 
+static jobject android_media_MediaDrm_getLogMessages(
+        JNIEnv *env, jobject thiz) {
+    sp<IDrm> drm = GetDrm(env, thiz);
+    if (!CheckDrm(env, drm)) {
+        return NULL;
+    }
+
+    Vector<drm::V1_4::LogMessage> logs;
+    status_t err = drm->getLogMessages(logs);
+    ALOGI("drm->getLogMessages %zu logs", logs.size());
+    if (throwExceptionAsNecessary(env, err, "Failed to get log messages")) {
+        return NULL;
+    }
+    return hidlLogMessagesToJavaList(env, logs);
+}
+
 static const JNINativeMethod gMethods[] = {
     { "native_release", "()V", (void *)android_media_MediaDrm_native_release },
 
@@ -2123,6 +2163,9 @@
 
     { "setPlaybackId", "([BLjava/lang/String;)V",
       (void *)android_media_MediaDrm_setPlaybackId },
+
+    { "getLogMessages", "()Ljava/util/List;",
+      (void *)android_media_MediaDrm_getLogMessages },
 };
 
 int register_android_media_Drm(JNIEnv *env) {
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java
index 72589e3..c30d4bf 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java
@@ -165,6 +165,7 @@
     protected void onStop() {
         super.onStop();
         if (!isFinishing() && !isChangingConfigurations()) {
+            Log.i(LOG_TAG, "onStop() - cancelling");
             cancel();
         }
     }
@@ -195,7 +196,6 @@
         titleView.setText(title);
     }
 
-    //TODO put in resources xmls
     private ProgressBar getProgressBar() {
         final ProgressBar progressBar = new ProgressBar(this);
         progressBar.setForegroundGravity(Gravity.CENTER_HORIZONTAL);
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
index 606cd57..67d4b41 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
@@ -309,7 +309,7 @@
     }
 
     private void onDeviceLost(@Nullable DeviceFilterPair device) {
-        if (DEBUG) Log.i(LOG_TAG, "Lost device " + device.getDisplayName());
+        Log.i(LOG_TAG, "Lost device " + device.getDisplayName());
         Handler.getMain().sendMessage(obtainMessage(
                 DeviceDiscoveryService::onDeviceLostMainThread, this, device));
     }
diff --git a/packages/Connectivity/framework/Android.bp b/packages/Connectivity/framework/Android.bp
index 8db8d76..73e1511 100644
--- a/packages/Connectivity/framework/Android.bp
+++ b/packages/Connectivity/framework/Android.bp
@@ -14,15 +14,37 @@
 // limitations under the License.
 //
 
-// TODO: use a java_library in the bootclasspath instead
 filegroup {
-    name: "framework-connectivity-sources",
+    name: "framework-connectivity-internal-sources",
     srcs: [
         "src/**/*.java",
         "src/**/*.aidl",
     ],
     path: "src",
     visibility: [
+        "//visibility:private",
+    ],
+}
+
+filegroup {
+    name: "framework-connectivity-aidl-export-sources",
+    srcs: [
+        "aidl-export/**/*.aidl",
+    ],
+    path: "aidl-export",
+    visibility: [
+        "//visibility:private",
+    ],
+}
+
+// TODO: use a java_library in the bootclasspath instead
+filegroup {
+    name: "framework-connectivity-sources",
+    srcs: [
+        ":framework-connectivity-internal-sources",
+        ":framework-connectivity-aidl-export-sources",
+    ],
+    visibility: [
         "//frameworks/base",
         "//packages/modules/Connectivity:__subpackages__",
     ],
diff --git a/packages/Connectivity/framework/src/android/net/CaptivePortalData.aidl b/packages/Connectivity/framework/aidl-export/android/net/CaptivePortalData.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/CaptivePortalData.aidl
rename to packages/Connectivity/framework/aidl-export/android/net/CaptivePortalData.aidl
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityDiagnosticsManager.aidl b/packages/Connectivity/framework/aidl-export/android/net/ConnectivityDiagnosticsManager.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/ConnectivityDiagnosticsManager.aidl
rename to packages/Connectivity/framework/aidl-export/android/net/ConnectivityDiagnosticsManager.aidl
diff --git a/packages/Connectivity/framework/src/android/net/DhcpInfo.aidl b/packages/Connectivity/framework/aidl-export/android/net/DhcpInfo.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/DhcpInfo.aidl
rename to packages/Connectivity/framework/aidl-export/android/net/DhcpInfo.aidl
diff --git a/packages/Connectivity/framework/src/android/net/IpConfiguration.aidl b/packages/Connectivity/framework/aidl-export/android/net/IpConfiguration.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/IpConfiguration.aidl
rename to packages/Connectivity/framework/aidl-export/android/net/IpConfiguration.aidl
diff --git a/packages/Connectivity/framework/src/android/net/IpPrefix.aidl b/packages/Connectivity/framework/aidl-export/android/net/IpPrefix.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/IpPrefix.aidl
rename to packages/Connectivity/framework/aidl-export/android/net/IpPrefix.aidl
diff --git a/packages/Connectivity/framework/src/android/net/KeepalivePacketData.aidl b/packages/Connectivity/framework/aidl-export/android/net/KeepalivePacketData.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/KeepalivePacketData.aidl
rename to packages/Connectivity/framework/aidl-export/android/net/KeepalivePacketData.aidl
diff --git a/packages/Connectivity/framework/src/android/net/LinkAddress.aidl b/packages/Connectivity/framework/aidl-export/android/net/LinkAddress.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/LinkAddress.aidl
rename to packages/Connectivity/framework/aidl-export/android/net/LinkAddress.aidl
diff --git a/packages/Connectivity/framework/src/android/net/LinkProperties.aidl b/packages/Connectivity/framework/aidl-export/android/net/LinkProperties.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/LinkProperties.aidl
rename to packages/Connectivity/framework/aidl-export/android/net/LinkProperties.aidl
diff --git a/packages/Connectivity/framework/src/android/net/MacAddress.aidl b/packages/Connectivity/framework/aidl-export/android/net/MacAddress.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/MacAddress.aidl
rename to packages/Connectivity/framework/aidl-export/android/net/MacAddress.aidl
diff --git a/packages/Connectivity/framework/src/android/net/Network.aidl b/packages/Connectivity/framework/aidl-export/android/net/Network.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/Network.aidl
rename to packages/Connectivity/framework/aidl-export/android/net/Network.aidl
diff --git a/packages/Connectivity/framework/src/android/net/NetworkAgentConfig.aidl b/packages/Connectivity/framework/aidl-export/android/net/NetworkAgentConfig.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/NetworkAgentConfig.aidl
rename to packages/Connectivity/framework/aidl-export/android/net/NetworkAgentConfig.aidl
diff --git a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.aidl b/packages/Connectivity/framework/aidl-export/android/net/NetworkCapabilities.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/NetworkCapabilities.aidl
rename to packages/Connectivity/framework/aidl-export/android/net/NetworkCapabilities.aidl
diff --git a/packages/Connectivity/framework/src/android/net/NetworkInfo.aidl b/packages/Connectivity/framework/aidl-export/android/net/NetworkInfo.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/NetworkInfo.aidl
rename to packages/Connectivity/framework/aidl-export/android/net/NetworkInfo.aidl
diff --git a/packages/Connectivity/framework/src/android/net/NetworkRequest.aidl b/packages/Connectivity/framework/aidl-export/android/net/NetworkRequest.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/NetworkRequest.aidl
rename to packages/Connectivity/framework/aidl-export/android/net/NetworkRequest.aidl
diff --git a/packages/Connectivity/framework/src/android/net/ProxyInfo.aidl b/packages/Connectivity/framework/aidl-export/android/net/ProxyInfo.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/ProxyInfo.aidl
rename to packages/Connectivity/framework/aidl-export/android/net/ProxyInfo.aidl
diff --git a/packages/Connectivity/framework/src/android/net/RouteInfo.aidl b/packages/Connectivity/framework/aidl-export/android/net/RouteInfo.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/RouteInfo.aidl
rename to packages/Connectivity/framework/aidl-export/android/net/RouteInfo.aidl
diff --git a/packages/Connectivity/framework/src/android/net/StaticIpConfiguration.aidl b/packages/Connectivity/framework/aidl-export/android/net/StaticIpConfiguration.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/StaticIpConfiguration.aidl
rename to packages/Connectivity/framework/aidl-export/android/net/StaticIpConfiguration.aidl
diff --git a/packages/Connectivity/framework/src/android/net/TestNetworkInterface.aidl b/packages/Connectivity/framework/aidl-export/android/net/TestNetworkInterface.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/TestNetworkInterface.aidl
rename to packages/Connectivity/framework/aidl-export/android/net/TestNetworkInterface.aidl
diff --git a/packages/Connectivity/framework/src/android/net/apf/ApfCapabilities.aidl b/packages/Connectivity/framework/aidl-export/android/net/apf/ApfCapabilities.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/apf/ApfCapabilities.aidl
rename to packages/Connectivity/framework/aidl-export/android/net/apf/ApfCapabilities.aidl
diff --git a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationAccesses.java b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationAccesses.java
index 81ca9ea..228de03 100644
--- a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationAccesses.java
+++ b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationAccesses.java
@@ -163,8 +163,12 @@
         long locationAccessFinishTime = 0L;
         // Earliest time for a location access to end and still be shown in list.
         long recentLocationCutoffTime = now - RECENT_TIME_INTERVAL_MILLIS;
+        // Compute the most recent access time from all op entries.
         for (AppOpsManager.OpEntry entry : entries) {
-            locationAccessFinishTime = entry.getLastAccessTime(TRUSTED_STATE_FLAGS);
+            long lastAccessTime = entry.getLastAccessTime(TRUSTED_STATE_FLAGS);
+            if (lastAccessTime > locationAccessFinishTime) {
+                locationAccessFinishTime = lastAccessTime;
+            }
         }
         // Bail out if the entry is out of date.
         if (locationAccessFinishTime < recentLocationCutoffTime) {
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index dbd25ce..a15ceb6 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -345,6 +345,12 @@
     <!-- Permissions required for CTS test - AdbManagerTest -->
     <uses-permission android:name="android.permission.MANAGE_DEBUGGING" />
 
+    <!-- Permission required for CTS test - CtsTelephonyTestCases -->
+    <uses-permission android:name="android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE" />
+
+    <!-- Permission required for CTS test - CtsTelephonyTestCases -->
+    <uses-permission android:name="android.permission.PERFORM_IMS_SINGLE_REGISTRATION" />
+
     <!-- Permission needed for CTS test - DisplayTest -->
     <uses-permission android:name="android.permission.OVERRIDE_DISPLAY_MODE_REQUESTS" />
 
diff --git a/packages/SystemUI/res-keyguard/drawable/ic_backspace_black_24dp.xml b/packages/SystemUI/res-keyguard/drawable/ic_backspace_24dp.xml
similarity index 90%
rename from packages/SystemUI/res-keyguard/drawable/ic_backspace_black_24dp.xml
rename to packages/SystemUI/res-keyguard/drawable/ic_backspace_24dp.xml
index 1f6b24b..dd35dd9 100644
--- a/packages/SystemUI/res-keyguard/drawable/ic_backspace_black_24dp.xml
+++ b/packages/SystemUI/res-keyguard/drawable/ic_backspace_24dp.xml
@@ -1,5 +1,5 @@
 <!--
-  ~ Copyright (C) 2017 The Android Open Source Project
+  ~ Copyright (C) 2021 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -20,6 +20,6 @@
         android:viewportWidth="24.0"
         android:viewportHeight="24.0">
     <path
-        android:fillColor="#FF000000"
+        android:fillColor="?android:attr/colorBackground"
         android:pathData="M9,15.59L12.59,12L9,8.41L10.41,7L14,10.59L17.59,7L19,8.41L15.41,12L19,15.59L17.59,17L14,13.41L10.41,17L9,15.59zM21,6H8l-4.5,6L8,18h13V6M21,4c1.1,0 2,0.9 2,2v12c0,1.1 -0.9,2 -2,2H8c-0.63,0 -1.22,-0.3 -1.6,-0.8L1,12l5.4,-7.2C6.78,4.3 7.37,4 8,4H21L21,4z"/>
 </vector>
diff --git a/packages/SystemUI/res-keyguard/drawable/num_pad_key_background.xml b/packages/SystemUI/res-keyguard/drawable/num_pad_key_background.xml
index b7a9fafd..604ab72 100644
--- a/packages/SystemUI/res-keyguard/drawable/num_pad_key_background.xml
+++ b/packages/SystemUI/res-keyguard/drawable/num_pad_key_background.xml
@@ -16,8 +16,23 @@
 * limitations under the License.
 */
 -->
-<shape
-    xmlns:android="http://schemas.android.com/apk/res/android">
-  <solid android:color="?android:attr/colorBackground" />
-  <corners android:radius="10dp" />
-</shape>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+  <item android:id="@+id/background">
+    <shape>
+      <solid android:color="?android:attr/colorControlNormal" />
+      <corners android:radius="10dp" />
+    </shape>
+  </item>
+  <item android:id="@+id/ripple">
+    <ripple
+        android:color="?android:attr/colorControlHighlight">
+      <item android:id="@android:id/mask">
+        <shape android:shape="rectangle">
+          <solid android:color="?android:attr/colorControlNormal" />
+          <corners android:radius="10dp" />
+        </shape>
+    </item>
+    </ripple>
+  </item>
+</layer-list>
+
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index a928b75..2a5784a 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -40,7 +40,7 @@
     <dimen name="keyguard_security_view_top_margin">8dp</dimen>
     <dimen name="keyguard_security_view_lateral_margin">36dp</dimen>
 
-    <dimen name="keyguard_eca_top_margin">24dp</dimen>
+    <dimen name="keyguard_eca_top_margin">18dp</dimen>
 
     <!-- EmergencyCarrierArea overlap - amount to overlap the emergency button and carrier text.
          Should be 0 on devices with plenty of room (e.g. tablets) -->
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index cd82b80..2391803 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -52,9 +52,8 @@
         <item name="android:textColor">?android:attr/textColorPrimary</item>
     </style>
     <style name="NumPadKey.Delete">
-        <item name="android:src">@drawable/ic_backspace_black_24dp</item>
-        <item name="android:tint">?android:attr/textColorSecondary</item>
-        <item name="android:tintMode">src_in</item>
+        <item name="android:colorControlNormal">?android:attr/textColorSecondary</item>
+        <item name="android:src">@drawable/ic_backspace_24dp</item>
     </style>
     <style name="NumPadKey.Enter">
       <item name="android:colorControlNormal">?android:attr/textColorSecondary</item>
diff --git a/packages/SystemUI/res/layout/people_space_notification_content_tile.xml b/packages/SystemUI/res/layout/people_space_notification_content_tile.xml
index 739738a..9ea7aa3 100644
--- a/packages/SystemUI/res/layout/people_space_notification_content_tile.xml
+++ b/packages/SystemUI/res/layout/people_space_notification_content_tile.xml
@@ -21,7 +21,7 @@
     android:orientation="vertical">
     <RelativeLayout
         android:background="@drawable/people_space_tile_view_card"
-        android:id="@+id/people_tile"
+        android:id="@+id/item"
         android:layout_width="match_parent"
         android:layout_height="match_parent">
         <include layout="@layout/punctuation_layout"/>
diff --git a/packages/SystemUI/res/layout/people_space_small_avatar_tile.xml b/packages/SystemUI/res/layout/people_space_small_avatar_tile.xml
index bb4a20e..3300495 100644
--- a/packages/SystemUI/res/layout/people_space_small_avatar_tile.xml
+++ b/packages/SystemUI/res/layout/people_space_small_avatar_tile.xml
@@ -21,7 +21,7 @@
     android:orientation="vertical">
     <RelativeLayout
         android:background="@drawable/people_space_tile_view_card"
-        android:id="@+id/people_tile"
+        android:id="@+id/item"
         android:layout_width="match_parent"
         android:layout_height="match_parent">
         <RelativeLayout
diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
index dc341274..059bda3 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -54,16 +54,4 @@
         android:paddingBottom="10dp"
         android:importantForAccessibility="yes" />
 
-    <TextView
-        android:id="@+id/header_debug_info"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_vertical"
-        android:fontFamily="sans-serif-condensed"
-        android:padding="2dp"
-        android:textColor="#00A040"
-        android:textSize="11dp"
-        android:textStyle="bold"
-        android:visibility="invisible"/>
-
 </com.android.systemui.qs.QuickStatusBarHeader>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index 825ea25..4e06491 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -24,14 +24,12 @@
 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
 
 import android.content.Context;
-import android.content.res.ColorStateList;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.KeyEvent;
 import android.view.View;
 
 import com.android.internal.widget.LockscreenCredential;
-import com.android.settingslib.Utils;
 import com.android.systemui.R;
 
 /**
@@ -188,10 +186,6 @@
             key.reloadColors();
         }
         mPasswordEntry.reloadColors();
-        int deleteColor = Utils.getColorAttr(getContext(), android.R.attr.textColorSecondary)
-                .getDefaultColor();
-        mDeleteButton.setImageTintList(ColorStateList.valueOf(deleteColor));
-
         mDeleteButton.reloadColors();
         mOkButton.reloadColors();
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
index cdf9858..97d6e97 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
@@ -17,14 +17,16 @@
 
 import android.animation.ValueAnimator;
 import android.content.Context;
+import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
 import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.LayerDrawable;
+import android.graphics.drawable.RippleDrawable;
 import android.view.ContextThemeWrapper;
 import android.view.ViewGroup;
 
 import androidx.annotation.StyleRes;
 
-import com.android.internal.graphics.ColorUtils;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 
@@ -34,13 +36,18 @@
 class NumPadAnimator {
     private ValueAnimator mAnimator;
     private GradientDrawable mBackground;
+    private RippleDrawable mRipple;
+    private GradientDrawable mRippleMask;
     private int mMargin;
     private int mNormalColor;
     private int mHighlightColor;
     private int mStyle;
 
-    NumPadAnimator(Context context, final GradientDrawable background, @StyleRes int style) {
-        mBackground = (GradientDrawable) background.mutate();
+    NumPadAnimator(Context context, LayerDrawable drawable, @StyleRes int style) {
+        LayerDrawable ld = (LayerDrawable) drawable.mutate();
+        mBackground = (GradientDrawable) ld.findDrawableByLayerId(R.id.background);
+        mRipple = (RippleDrawable) ld.findDrawableByLayerId(R.id.ripple);
+        mRippleMask = (GradientDrawable) mRipple.findDrawableByLayerId(android.R.id.mask);
         mStyle = style;
 
         reloadColors(context);
@@ -49,13 +56,14 @@
 
         // Actual values will be updated later, usually during an onLayout() call
         mAnimator = ValueAnimator.ofFloat(0f);
-        mAnimator.setDuration(250);
-        mAnimator.setInterpolator(Interpolators.LINEAR);
+        mAnimator.setDuration(100);
+        mAnimator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
+        mAnimator.setRepeatMode(ValueAnimator.REVERSE);
+        mAnimator.setRepeatCount(1);
         mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                 public void onAnimationUpdate(ValueAnimator anim) {
                     mBackground.setCornerRadius((float) anim.getAnimatedValue());
-                    mBackground.setColor(ColorUtils.blendARGB(mHighlightColor, mNormalColor,
-                            anim.getAnimatedFraction()));
+                    mRippleMask.setCornerRadius((float) anim.getAnimatedValue());
                 }
         });
 
@@ -66,9 +74,9 @@
     }
 
     void onLayout(int height) {
-        float startRadius = height / 10f;
-        float endRadius = height / 2f;
-        mBackground.setCornerRadius(endRadius);
+        float startRadius = height / 2f;
+        float endRadius = height / 4f;
+        mBackground.setCornerRadius(startRadius);
         mAnimator.setFloatValues(startRadius, endRadius);
     }
 
@@ -91,6 +99,7 @@
         a.recycle();
 
         mBackground.setColor(mNormalColor);
+        mRipple.setColor(ColorStateList.valueOf(mHighlightColor));
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
index 886c372..8cb1bc4 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
@@ -17,7 +17,7 @@
 
 import android.content.Context;
 import android.content.res.ColorStateList;
-import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.LayerDrawable;
 import android.graphics.drawable.VectorDrawable;
 import android.util.AttributeSet;
 import android.view.ContextThemeWrapper;
@@ -37,7 +37,7 @@
     public NumPadButton(Context context, AttributeSet attrs) {
         super(context, attrs);
 
-        mAnimator = new NumPadAnimator(context, (GradientDrawable) getBackground(),
+        mAnimator = new NumPadAnimator(context, (LayerDrawable) getBackground(),
                 attrs.getStyleAttribute());
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
index 01e1c63..a4a781d 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
@@ -18,7 +18,7 @@
 
 import android.content.Context;
 import android.content.res.TypedArray;
-import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.LayerDrawable;
 import android.os.PowerManager;
 import android.os.SystemClock;
 import android.util.AttributeSet;
@@ -127,7 +127,7 @@
 
         setContentDescription(mDigitText.getText().toString());
 
-        mAnimator = new NumPadAnimator(context, (GradientDrawable) getBackground(),
+        mAnimator = new NumPadAnimator(context, (LayerDrawable) getBackground(),
                 R.style.NumPadKey);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
index 40c2386..fc89783 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
@@ -32,6 +32,8 @@
 import com.android.systemui.controls.CustomIconCache
 import com.android.systemui.controls.controller.ControlsControllerImpl
 import com.android.systemui.controls.controller.StructureInfo
+import com.android.systemui.controls.ui.ControlsDialog
+import com.android.systemui.controls.ui.ControlsUiController
 import com.android.systemui.globalactions.GlobalActionsComponent
 import com.android.systemui.settings.CurrentUserTracker
 import com.android.systemui.util.LifecycleActivity
@@ -42,9 +44,10 @@
  */
 class ControlsEditingActivity @Inject constructor(
     private val controller: ControlsControllerImpl,
-    broadcastDispatcher: BroadcastDispatcher,
+    private val broadcastDispatcher: BroadcastDispatcher,
     private val globalActionsComponent: GlobalActionsComponent,
-    private val customIconCache: CustomIconCache
+    private val customIconCache: CustomIconCache,
+    private val uiController: ControlsUiController
 ) : LifecycleActivity() {
 
     companion object {
@@ -59,6 +62,7 @@
     private lateinit var model: FavoritesModel
     private lateinit var subtitle: TextView
     private lateinit var saveButton: View
+    private var backToGlobalActions = true
 
     private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
         private val startingUser = controller.currentUserId
@@ -82,6 +86,11 @@
             structure = it
         } ?: run(this::finish)
 
+        backToGlobalActions = intent.getBooleanExtra(
+            ControlsUiController.BACK_TO_GLOBAL_ACTIONS,
+            true
+        )
+
         bindViews()
 
         bindButtons()
@@ -100,7 +109,11 @@
     }
 
     override fun onBackPressed() {
-        globalActionsComponent.handleShowGlobalActionsMenu()
+        if (backToGlobalActions) {
+            globalActionsComponent.handleShowGlobalActionsMenu()
+        } else {
+            ControlsDialog(applicationContext, broadcastDispatcher).show(uiController)
+        }
         animateExitAndFinish()
     }
 
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 b282157..1c2f17c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -40,6 +40,8 @@
 import com.android.systemui.controls.TooltipManager
 import com.android.systemui.controls.controller.ControlsControllerImpl
 import com.android.systemui.controls.controller.StructureInfo
+import com.android.systemui.controls.ui.ControlsDialog
+import com.android.systemui.controls.ui.ControlsUiController
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.globalactions.GlobalActionsComponent
 import com.android.systemui.settings.CurrentUserTracker
@@ -53,8 +55,9 @@
     @Main private val executor: Executor,
     private val controller: ControlsControllerImpl,
     private val listingController: ControlsListingController,
-    broadcastDispatcher: BroadcastDispatcher,
-    private val globalActionsComponent: GlobalActionsComponent
+    private val broadcastDispatcher: BroadcastDispatcher,
+    private val globalActionsComponent: GlobalActionsComponent,
+    private val uiController: ControlsUiController
 ) : LifecycleActivity() {
 
     companion object {
@@ -89,6 +92,7 @@
     private lateinit var comparator: Comparator<StructureContainer>
     private var cancelLoadRunnable: Runnable? = null
     private var isPagerLoaded = false
+    private var backToGlobalActions = true
 
     private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
         private val startingUser = controller.currentUserId
@@ -114,7 +118,7 @@
 
     override fun onBackPressed() {
         if (!fromProviderSelector) {
-            globalActionsComponent.handleShowGlobalActionsMenu()
+            openControlsOrigin()
         }
         animateExitAndFinish()
     }
@@ -129,6 +133,11 @@
         component = intent.getParcelableExtra<ComponentName>(Intent.EXTRA_COMPONENT_NAME)
         fromProviderSelector = intent.getBooleanExtra(EXTRA_FROM_PROVIDER_SELECTOR, false)
 
+        backToGlobalActions = intent.getBooleanExtra(
+            ControlsUiController.BACK_TO_GLOBAL_ACTIONS,
+            true
+        )
+
         bindViews()
     }
 
@@ -330,11 +339,19 @@
                     )
                 }
                 animateExitAndFinish()
-                globalActionsComponent.handleShowGlobalActionsMenu()
+                openControlsOrigin()
             }
         }
     }
 
+    private fun openControlsOrigin() {
+        if (backToGlobalActions) {
+            globalActionsComponent.handleShowGlobalActionsMenu()
+        } else {
+            ControlsDialog(applicationContext, broadcastDispatcher).show(uiController)
+        }
+    }
+
     override fun onPause() {
         super.onPause()
         mTooltipManager?.hide(false)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt
index db68d17..537334a 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt
@@ -67,7 +67,7 @@
 
         val vg = requireViewById<ViewGroup>(com.android.systemui.R.id.global_actions_controls)
         vg.alpha = 0f
-        controller.show(vg, { /* do nothing */ }, false /* startedFromGlobalActions */)
+        controller.show(vg, { dismiss() }, false /* startedFromGlobalActions */)
 
         vg.animate()
             .alpha(1f)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
index 9448877..20bdf60 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
@@ -27,6 +27,7 @@
     companion object {
         public const val TAG = "ControlsUiController"
         public const val EXTRA_ANIMATE = "extra_animate"
+        public const val BACK_TO_GLOBAL_ACTIONS = "back_to_global_actions"
     }
 
     fun show(parent: ViewGroup, onDismiss: Runnable, startedFromGlobalActions: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 762362c..c94d85a 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -266,6 +266,10 @@
     private fun startActivity(context: Context, intent: Intent) {
         // Force animations when transitioning from a dialog to an activity
         intent.putExtra(ControlsUiController.EXTRA_ANIMATE, true)
+        intent.putExtra(
+            ControlsUiController.BACK_TO_GLOBAL_ACTIONS,
+            controlActionCoordinator.startedFromGlobalActions
+        )
         onDismiss.run()
 
         activityStarter.dismissKeyguardThenExecute({
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java
index d65d169..2873cd3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.content.res.ColorStateList;
 import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
 import android.view.View;
 
 /**
@@ -56,7 +57,7 @@
     /**
      * Message to display
      */
-    public @NonNull CharSequence getMessage() {
+    public @Nullable CharSequence getMessage() {
         return mMessage;
     }
 
@@ -88,6 +89,17 @@
         return mBackground;
     }
 
+    @Override
+    public String toString() {
+        String str = "KeyguardIndication{";
+        if (!TextUtils.isEmpty(mMessage)) str += "mMessage=" + mMessage;
+        if (mIcon != null) str += " mIcon=" + mIcon;
+        if (mOnClickListener != null) str += " mOnClickListener=" + mOnClickListener;
+        if (mBackground != null) str += " mBackground=" + mBackground;
+        str += "}";
+        return str;
+    }
+
     /**
      * KeyguardIndication Builder
      */
@@ -101,7 +113,7 @@
         public Builder() { }
 
         /**
-         * Required field. Message to display.
+         * Message to display. Indication requires a non-null message or icon.
          */
         public Builder setMessage(@NonNull CharSequence message) {
             this.mMessage = message;
@@ -117,9 +129,9 @@
         }
 
         /**
-         * Optional. Icon to show next to the text. Icon location changes based on language
-         * display direction. For LTR, icon shows to the left of the message. For RTL, icon shows
-         * to the right of the message.
+         * Icon to show next to the text. Indication requires a non-null icon or message.
+         * Icon location changes based on language display direction. For LTR, icon shows to the
+         * left of the message. For RTL, icon shows to the right of the message.
          */
         public Builder setIcon(Drawable icon) {
             this.mIcon = icon;
@@ -146,7 +158,7 @@
          * Build the KeyguardIndication.
          */
         public KeyguardIndication build() {
-            if (mMessage == null && mIcon == null) {
+            if (TextUtils.isEmpty(mMessage) && mIcon == null) {
                 throw new IllegalStateException("message or icon must be set");
             }
             if (mTextColor == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
index 2e599de..d4678f3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
@@ -19,7 +19,6 @@
 import android.annotation.Nullable;
 import android.content.res.ColorStateList;
 import android.graphics.Color;
-import android.text.TextUtils;
 import android.view.View;
 
 import androidx.annotation.IntDef;
@@ -105,9 +104,7 @@
     public void updateIndication(@IndicationType int type, KeyguardIndication newIndication,
             boolean showImmediately) {
         final boolean hasPreviousIndication = mIndicationMessages.get(type) != null;
-        final boolean hasNewIndication = newIndication != null
-                && (!TextUtils.isEmpty(newIndication.getMessage())
-                    || newIndication.getIcon() != null);
+        final boolean hasNewIndication = newIndication != null;
         if (!hasNewIndication) {
             mIndicationMessages.remove(type);
             mIndicationQueue.removeIf(x -> x == type);
@@ -289,8 +286,7 @@
         if (hasIndications()) {
             pw.println("    All messages:");
             for (int type : mIndicationMessages.keySet()) {
-                pw.println("        type=" + type
-                        + " message=" + mIndicationMessages.get(type).getMessage());
+                pw.println("        type=" + type + " " + mIndicationMessages.get(type));
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index dab4d0b..1660dea 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -855,6 +855,14 @@
 
     @Override
     public void onRotationProposal(final int rotation, boolean isValid) {
+        if (mNavigationBarView == null) {
+            if (RotationContextButton.DEBUG_ROTATION) {
+                Log.v(TAG, "onRotationProposal proposedRotation=" +
+                        Surface.rotationToString(rotation) + ", mNavigationBarView is null");
+            }
+            return;
+        }
+
         final int winRotation = mNavigationBarView.getDisplay().getRotation();
         final boolean rotateSuggestionsDisabled = RotationButtonController
                 .hasDisable2RotateSuggestionFlag(mDisabledFlags2);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 87252ff..a0bf584 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -90,6 +90,7 @@
     private OngoingPrivacyChip mPrivacyChip;
     private Space mSpace;
     private BatteryMeterView mBatteryRemainingIcon;
+    private TintedIconManager mTintedIconManager;
 
     // Used for RingerModeTracker
     private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
@@ -144,6 +145,7 @@
     }
 
     void onAttach(TintedIconManager iconManager) {
+        mTintedIconManager = iconManager;
         int fillColor = Utils.getColorAttrDefaultColor(getContext(),
                 android.R.attr.textColorPrimary);
 
@@ -268,6 +270,9 @@
                     android.R.attr.textColorSecondary);
             mTextColorPrimary = textColor;
             mClockView.setTextColor(textColor);
+            if (mTintedIconManager != null) {
+                mTintedIconManager.setTint(textColor);
+            }
             mBatteryRemainingIcon.updateColors(mTextColorPrimary, textColorSecondary,
                     mTextColorPrimary);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index c70a93b..7e2d27a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -208,7 +208,6 @@
                 mLockScreenMode);
         updateIndication(false /* animate */);
         updateDisclosure();
-        updateOwnerInfo();
         if (mBroadcastReceiver == null) {
             // Update the disclosure proactively to avoid IPC on the critical path.
             mBroadcastReceiver = new BroadcastReceiver() {
@@ -261,18 +260,21 @@
     }
 
     /**
-     * Doesn't include owner information or disclosure which get triggered separately.
+     * Doesn't include disclosure which gets triggered separately.
      */
     private void updateIndications(boolean animate, int userId) {
+        updateOwnerInfo();
         updateBattery(animate);
         updateUserLocked(userId);
         updateTransient();
         updateTrust(userId, getTrustGrantedIndication(), getTrustManagedIndication());
         updateAlignment();
+        updateLogoutView();
         updateResting();
     }
 
     private void updateDisclosure() {
+        // avoid calling this method since it has an IPC
         if (whitelistIpcs(this::isOrganizationOwnedDevice)) {
             final CharSequence organizationName = getOrganizationOwnedDeviceOrganizationName();
             final CharSequence disclosure =  organizationName != null
@@ -291,7 +293,34 @@
         }
 
         if (isKeyguardLayoutEnabled()) {
-            updateIndication(false); // resting indication may need to update
+            updateResting();
+        }
+    }
+
+    private void updateOwnerInfo() {
+        if (!isKeyguardLayoutEnabled()) {
+            mRotateTextViewController.hideIndication(INDICATION_TYPE_OWNER_INFO);
+            return;
+        }
+        String info = mLockPatternUtils.getDeviceOwnerInfo();
+        if (info == null) {
+            // Use the current user owner information if enabled.
+            final boolean ownerInfoEnabled = mLockPatternUtils.isOwnerInfoEnabled(
+                    KeyguardUpdateMonitor.getCurrentUser());
+            if (ownerInfoEnabled) {
+                info = mLockPatternUtils.getOwnerInfo(KeyguardUpdateMonitor.getCurrentUser());
+            }
+        }
+        if (info != null) {
+            mRotateTextViewController.updateIndication(
+                    INDICATION_TYPE_OWNER_INFO,
+                    new KeyguardIndication.Builder()
+                            .setMessage(info)
+                            .setTextColor(mInitialTextColorState)
+                            .build(),
+                    false);
+        } else {
+            mRotateTextViewController.hideIndication(INDICATION_TYPE_OWNER_INFO);
         }
     }
 
@@ -400,56 +429,34 @@
 
     private void updateLogoutView() {
         if (!isKeyguardLayoutEnabled()) {
+            mRotateTextViewController.hideIndication(INDICATION_TYPE_LOGOUT);
             return;
         }
         final boolean shouldShowLogout = mKeyguardUpdateMonitor.isLogoutEnabled()
                 && KeyguardUpdateMonitor.getCurrentUser() != UserHandle.USER_SYSTEM;
-        String logoutString = shouldShowLogout ? mContext.getResources().getString(
-                    com.android.internal.R.string.global_action_logout) : null;
-        mRotateTextViewController.updateIndication(
-                INDICATION_TYPE_LOGOUT,
-                new KeyguardIndication.Builder()
-                        .setMessage(logoutString)
-                        .setTextColor(mInitialTextColorState)
-                        .setBackground(mContext.getDrawable(
-                                com.android.systemui.R.drawable.logout_button_background))
-                        .setClickListener((view) -> {
-                            int currentUserId = KeyguardUpdateMonitor.getCurrentUser();
-                            try {
-                                mIActivityManager.switchUser(UserHandle.USER_SYSTEM);
-                                mIActivityManager.stopUser(currentUserId, true /* force */, null);
-                            } catch (RemoteException re) {
-                                Log.e(TAG, "Failed to logout user", re);
-                            }
-                        })
-                .build(),
-                false);
-        updateIndication(false); // resting indication may need to update
-    }
-
-    private void updateOwnerInfo() {
-        if (!isKeyguardLayoutEnabled()) {
-            return;
-        }
-        String info = mLockPatternUtils.getDeviceOwnerInfo();
-        if (info == null) {
-            // Use the current user owner information if enabled.
-            final boolean ownerInfoEnabled = mLockPatternUtils.isOwnerInfoEnabled(
-                    KeyguardUpdateMonitor.getCurrentUser());
-            if (ownerInfoEnabled) {
-                info = mLockPatternUtils.getOwnerInfo(KeyguardUpdateMonitor.getCurrentUser());
-            }
-        }
-        if (info != null) {
+        if (shouldShowLogout) {
             mRotateTextViewController.updateIndication(
-                    INDICATION_TYPE_OWNER_INFO,
+                    INDICATION_TYPE_LOGOUT,
                     new KeyguardIndication.Builder()
-                            .setMessage(info)
+                            .setMessage(mContext.getResources().getString(
+                                    com.android.internal.R.string.global_action_logout))
                             .setTextColor(mInitialTextColorState)
+                            .setBackground(mContext.getDrawable(
+                                    com.android.systemui.R.drawable.logout_button_background))
+                            .setClickListener((view) -> {
+                                int currentUserId = KeyguardUpdateMonitor.getCurrentUser();
+                                try {
+                                    mIActivityManager.switchUser(UserHandle.USER_SYSTEM);
+                                    mIActivityManager.stopUser(currentUserId, true /* force */,
+                                            null);
+                                } catch (RemoteException re) {
+                                    Log.e(TAG, "Failed to logout user", re);
+                                }
+                            })
                             .build(),
                     false);
         } else {
-            updateIndication(false); // resting indication may need to update
+            mRotateTextViewController.hideIndication(INDICATION_TYPE_LOGOUT);
         }
     }
 
@@ -1042,7 +1049,6 @@
         @Override
         public void onUserSwitchComplete(int userId) {
             if (mVisible) {
-                updateOwnerInfo();
                 updateIndication(false);
             }
         }
@@ -1057,7 +1063,7 @@
         @Override
         public void onLogoutEnabledChanged() {
             if (mVisible) {
-                updateLogoutView();
+                updateIndication(false);
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
index d562726..138c811 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
@@ -24,7 +24,6 @@
 
 import android.content.Context;
 import android.content.res.ColorStateList;
-import android.graphics.Color;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.util.FeatureFlagUtils;
@@ -238,11 +237,7 @@
     @Override
     public void setStaticDrawableColor(int color) {
         ColorStateList list = ColorStateList.valueOf(color);
-        float intensity = color == Color.WHITE ? 0 : 1;
-        // We want the ability to change the theme from the one set by SignalDrawable in certain
-        // surfaces. In this way, we can pass a theme to the view.
-        mMobileDrawable.setTintList(
-                ColorStateList.valueOf(mDualToneHandler.getSingleColor(intensity)));
+        mMobileDrawable.setTintList(list);
         mIn.setImageTintList(list);
         mOut.setImageTintList(list);
         mMobileType.setImageTintList(list);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 8aadef8..2f9fa9e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -68,7 +68,6 @@
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.FrameLayout;
-import android.widget.TextView;
 
 import androidx.constraintlayout.widget.ConstraintSet;
 
@@ -405,6 +404,7 @@
     // Used for two finger gesture as well as accessibility shortcut to QS.
     private boolean mQsExpandImmediate;
     private boolean mTwoFingerQsExpandPossible;
+    private String mHeaderDebugInfo;
 
     /**
      * If we are in a panel collapsing motion, we reset scrollY of our scroll view but still
@@ -3423,8 +3423,8 @@
         return mView.getHeight();
     }
 
-    public TextView getHeaderDebugInfo() {
-        return mView.findViewById(R.id.header_debug_info);
+    public void setHeaderDebugInfo(String text) {
+        if (DEBUG) mHeaderDebugInfo = text;
     }
 
     public void onThemeChanged() {
@@ -4087,6 +4087,8 @@
             p.setStrokeWidth(2);
             p.setStyle(Paint.Style.STROKE);
             canvas.drawLine(0, getMaxPanelHeight(), mView.getWidth(), getMaxPanelHeight(), p);
+            p.setTextSize(24);
+            if (mHeaderDebugInfo != null) canvas.drawText(mHeaderDebugInfo, 50, 100, p);
             p.setColor(Color.BLUE);
             canvas.drawLine(0, getExpandedHeight(), mView.getWidth(), getExpandedHeight(), p);
             p.setColor(Color.GREEN);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 024a0b1..525f220 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -29,7 +29,6 @@
 import android.service.vr.IVrStateCallbacks;
 import android.util.Log;
 import android.util.Slog;
-import android.view.View;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.TextView;
 
@@ -162,11 +161,6 @@
         mBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
 
-        if (MULTIUSER_DEBUG) {
-            mNotificationPanelDebugText = mNotificationPanel.getHeaderDebugInfo();
-            mNotificationPanelDebugText.setVisibility(View.VISIBLE);
-        }
-
         IVrManager vrManager = IVrManager.Stub.asInterface(ServiceManager.getService(
                 Context.VR_SERVICE));
         if (vrManager != null) {
@@ -332,7 +326,7 @@
         // Begin old BaseStatusBar.userSwitched
         mHeadsUpManager.setUser(newUserId);
         // End old BaseStatusBar.userSwitched
-        if (MULTIUSER_DEBUG) mNotificationPanelDebugText.setText("USER " + newUserId);
+        if (MULTIUSER_DEBUG) mNotificationPanel.setHeaderDebugInfo("USER " + newUserId);
         mCommandQueue.animateCollapsePanels();
         if (mReinflateNotificationsOnUserSwitched) {
             updateNotificationsOnDensityOrFontScaleChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
index 9e78a66..0a3e833 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
@@ -65,6 +65,8 @@
             "android.theme.customization.accent_color";
     static final String OVERLAY_CATEGORY_SYSTEM_PALETTE =
             "android.theme.customization.system_palette";
+    static final String OVERLAY_CATEGORY_NEUTRAL_PALETTE =
+            "android.theme.customization.neutral_palette";
     @VisibleForTesting
     static final String OVERLAY_CATEGORY_FONT = "android.theme.customization.font";
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 522a42b..1f222d8 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -16,6 +16,7 @@
 package com.android.systemui.theme;
 
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR;
+import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_NEUTRAL_PALETTE;
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE;
 
 import android.annotation.Nullable;
@@ -83,8 +84,9 @@
     protected static final String TAG = "ThemeOverlayController";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
-    protected static final int MAIN = 0;
-    protected static final int ACCENT = 1;
+    protected static final int PRIMARY = 0;
+    protected static final int SECONDARY = 1;
+    protected static final int NEUTRAL = 1;
 
     // If lock screen wallpaper colors should also be considered when selecting the theme.
     // Doing this has performance impact, given that overlays would need to be swapped when
@@ -111,9 +113,11 @@
     // Accent color extracted from wallpaper, NOT the color used on the overlay
     protected int mWallpaperAccentColor = Color.TRANSPARENT;
     // System colors overlay
-    private FabricatedOverlay mSystemOverlay;
+    private FabricatedOverlay mPrimaryOverlay;
     // Accent colors overlay
-    private FabricatedOverlay mAccentOverlay;
+    private FabricatedOverlay mSecondaryOverlay;
+    // Neutral system colors overlay
+    private FabricatedOverlay mNeutralOverlay;
 
     @Inject
     public ThemeOverlayController(Context context, BroadcastDispatcher broadcastDispatcher,
@@ -232,12 +236,13 @@
         mWallpaperAccentColor = accentCandidate;
 
         if (mIsMonetEnabled) {
-            mSystemOverlay = getOverlay(mMainWallpaperColor, MAIN);
-            mAccentOverlay = getOverlay(mWallpaperAccentColor, ACCENT);
+            mPrimaryOverlay = getOverlay(mMainWallpaperColor, PRIMARY);
+            mSecondaryOverlay = getOverlay(mWallpaperAccentColor, SECONDARY);
+            mNeutralOverlay = getOverlay(mMainWallpaperColor, NEUTRAL);
             mNeedsOverlayCreation = true;
             if (DEBUG) {
-                Log.d(TAG, "fetched overlays. system: " + mSystemOverlay + " accent: "
-                        + mAccentOverlay);
+                Log.d(TAG, "fetched overlays. primary: " + mPrimaryOverlay + " secondary: "
+                        + mSecondaryOverlay + " neutral: " + mNeutralOverlay);
             }
         }
 
@@ -296,7 +301,9 @@
         if (mIsMonetEnabled && systemPalette != null && systemPalette.getPackageName() != null) {
             try {
                 int color = Integer.parseInt(systemPalette.getPackageName().toLowerCase(), 16);
-                mSystemOverlay = getOverlay(color, MAIN);
+                mPrimaryOverlay = getOverlay(color, PRIMARY);
+                // Neutral palette is always derived from primary color.
+                mNeutralOverlay = getOverlay(color, NEUTRAL);
                 mNeedsOverlayCreation = true;
                 categoryToPackage.remove(OVERLAY_CATEGORY_SYSTEM_PALETTE);
             } catch (NumberFormatException e) {
@@ -309,7 +316,7 @@
         if (mIsMonetEnabled && accentPalette != null && accentPalette.getPackageName() != null) {
             try {
                 int color = Integer.parseInt(accentPalette.getPackageName().toLowerCase(), 16);
-                mAccentOverlay = getOverlay(color, ACCENT);
+                mSecondaryOverlay = getOverlay(color, SECONDARY);
                 mNeedsOverlayCreation = true;
                 categoryToPackage.remove(OVERLAY_CATEGORY_ACCENT_COLOR);
             } catch (NumberFormatException e) {
@@ -320,12 +327,14 @@
         // Compatibility with legacy themes, where full packages were defined, instead of just
         // colors.
         if (!categoryToPackage.containsKey(OVERLAY_CATEGORY_SYSTEM_PALETTE)
-                && mSystemOverlay != null) {
-            categoryToPackage.put(OVERLAY_CATEGORY_SYSTEM_PALETTE, mSystemOverlay.getIdentifier());
+                && mPrimaryOverlay != null) {
+            categoryToPackage.put(OVERLAY_CATEGORY_SYSTEM_PALETTE, mPrimaryOverlay.getIdentifier());
+            categoryToPackage.put(OVERLAY_CATEGORY_NEUTRAL_PALETTE,
+                    mNeutralOverlay.getIdentifier());
         }
         if (!categoryToPackage.containsKey(OVERLAY_CATEGORY_ACCENT_COLOR)
-                && mAccentOverlay != null) {
-            categoryToPackage.put(OVERLAY_CATEGORY_ACCENT_COLOR, mAccentOverlay.getIdentifier());
+                && mSecondaryOverlay != null) {
+            categoryToPackage.put(OVERLAY_CATEGORY_ACCENT_COLOR, mSecondaryOverlay.getIdentifier());
         }
 
         Set<UserHandle> userHandles = Sets.newHashSet(UserHandle.of(currentUser));
@@ -342,7 +351,7 @@
         if (mNeedsOverlayCreation) {
             mNeedsOverlayCreation = false;
             mThemeManager.applyCurrentUserOverlays(categoryToPackage, new FabricatedOverlay[] {
-                    mSystemOverlay, mAccentOverlay
+                    mPrimaryOverlay, mSecondaryOverlay, mNeutralOverlay
             }, userHandles);
         } else {
             mThemeManager.applyCurrentUserOverlays(categoryToPackage, null, userHandles);
@@ -356,8 +365,9 @@
         pw.println("mSystemColors=" + mSystemColors);
         pw.println("mMainWallpaperColor=" + Integer.toHexString(mMainWallpaperColor));
         pw.println("mWallpaperAccentColor=" + Integer.toHexString(mWallpaperAccentColor));
-        pw.println("mSystemOverlayColor=" + mSystemOverlay);
-        pw.println("mAccentOverlayColor=" + mAccentOverlay);
+        pw.println("mPrimaryOverlay=" + mPrimaryOverlay);
+        pw.println("mSecondaryOverlay=" + mSecondaryOverlay);
+        pw.println("mNeutralOverlay=" + mNeutralOverlay);
         pw.println("mIsMonetEnabled=" + mIsMonetEnabled);
         pw.println("mNeedsOverlayCreation=" + mNeedsOverlayCreation);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationTest.java
new file mode 100644
index 0000000..b44fb8e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard;
+
+import static android.graphics.Color.WHITE;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.res.ColorStateList;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.drawable.Drawable;
+import android.testing.AndroidTestingRunner;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class KeyguardIndicationTest extends SysuiTestCase {
+
+    @Test(expected = IllegalStateException.class)
+    public void testCannotCreateIndicationWithoutMessageOrIcon() {
+        new KeyguardIndication.Builder()
+                .setTextColor(ColorStateList.valueOf(WHITE))
+                .build();
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testCannotCreateIndicationWithoutColor() {
+        new KeyguardIndication.Builder()
+                .setMessage("message")
+                .build();
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testCannotCreateIndicationWithEmptyMessage() {
+        new KeyguardIndication.Builder()
+                .setMessage("")
+                .setTextColor(ColorStateList.valueOf(WHITE))
+                .build();
+    }
+
+    @Test
+    public void testCreateIndicationWithMessage() {
+        final String text = "regular indication";
+        final KeyguardIndication indication = new KeyguardIndication.Builder()
+                .setMessage(text)
+                .setTextColor(ColorStateList.valueOf(WHITE))
+                .build();
+        assertEquals(text, indication.getMessage());
+    }
+
+    @Test
+    public void testCreateIndicationWithIcon() {
+        final KeyguardIndication indication = new KeyguardIndication.Builder()
+                .setIcon(mDrawable)
+                .setTextColor(ColorStateList.valueOf(WHITE))
+                .build();
+        assertEquals(mDrawable, indication.getIcon());
+    }
+
+    @Test
+    public void testCreateIndicationWithMessageAndIcon() {
+        final String text = "indication with msg and icon";
+        final KeyguardIndication indication = new KeyguardIndication.Builder()
+                .setMessage(text)
+                .setIcon(mDrawable)
+                .setTextColor(ColorStateList.valueOf(WHITE))
+                .build();
+        assertEquals(text, indication.getMessage());
+        assertEquals(mDrawable, indication.getIcon());
+    }
+
+    final Drawable mDrawable = new Drawable() {
+        @Override
+        public void draw(@NonNull Canvas canvas) { }
+
+        @Override
+        public void setAlpha(int alpha) { }
+
+        @Override
+        public void setColorFilter(@Nullable ColorFilter colorFilter) { }
+
+        @Override
+        public int getOpacity() {
+            return 0;
+        }
+    };
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index f7f8d03..aa385ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -17,6 +17,7 @@
 package com.android.systemui.theme;
 
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR;
+import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_NEUTRAL_PALETTE;
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE;
 import static com.android.systemui.theme.ThemeOverlayController.USE_LOCK_SCREEN_WALLPAPER;
 
@@ -147,6 +148,8 @@
         // Assert that we received the colors that we were expecting
         assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
                 .isEqualTo(new OverlayIdentifier("ffff0000"));
+        assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_NEUTRAL_PALETTE))
+                .isEqualTo(new OverlayIdentifier("ffff0000"));
         assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_ACCENT_COLOR))
                 .isEqualTo(new OverlayIdentifier("ff0000ff"));
 
diff --git a/packages/services/CameraExtensionsProxy/Android.bp b/packages/services/CameraExtensionsProxy/Android.bp
index 54b7453..e2e4af2 100644
--- a/packages/services/CameraExtensionsProxy/Android.bp
+++ b/packages/services/CameraExtensionsProxy/Android.bp
@@ -18,6 +18,7 @@
     name: "CameraExtensionsProxy",
     srcs: ["src/**/*.java"],
     libs: ["androidx.camera.extensions.stub"],
+    optional_uses_libs: ["androidx.camera.extensions.impl"],
     platform_apis: true,
     certificate: "platform",
 }
diff --git a/services/Android.bp b/services/Android.bp
index 3154628..8aae8e6 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -6,7 +6,7 @@
 }
 
 filegroup {
-    name: "services-all-sources",
+    name: "services-non-updatable-sources",
     srcs: [
         ":services.core-sources",
         ":services.core-sources-am-wm",
@@ -33,12 +33,19 @@
         ":services.startop.iorap-sources",
         ":services.systemcaptions-sources",
         ":services.translation-sources",
-        ":services.texttospeech-sources",
         ":services.usage-sources",
         ":services.usb-sources",
         ":services.voiceinteraction-sources",
         ":services.wifi-sources",
-        ":service-media-s-sources", // TODO (b/177640454)
+    ],
+    visibility: ["//visibility:private"],
+}
+
+filegroup {
+    name: "services-all-sources",
+    srcs: [
+        ":services-non-updatable-sources",
+        ":service-media-s-sources",
         ":service-permission-sources",
         ":service-statsd-sources",
     ],
@@ -84,7 +91,6 @@
         "services.startop",
         "services.systemcaptions",
         "services.translation",
-        "services.texttospeech",
         "services.usage",
         "services.usb",
         "services.voiceinteraction",
@@ -125,9 +131,8 @@
 // API stub
 // =============================================================
 
-droidstubs {
-    name: "services-stubs.sources",
-    srcs: [":services-all-sources"],
+stubs_defaults {
+    name: "services-stubs-default",
     installable: false,
     args: " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.SYSTEM_SERVER\\)" +
         " --hide-annotation android.annotation.Hide" +
@@ -137,7 +142,13 @@
         " --hide DeprecationMismatch" +
         " --hide HiddenTypedefConstant",
     visibility: ["//visibility:private"],
-    filter_packages: ["com.android."],
+    filter_packages: ["com.android."]
+}
+
+droidstubs {
+    name: "services-stubs.sources",
+    srcs: [":services-all-sources"],
+    defaults: ["services-stubs-default"],
     check_api: {
         current: {
             api_file: "api/current.txt",
@@ -183,3 +194,34 @@
         dir: "apistubs/android/system-server",
     },
 }
+
+droidstubs {
+    name: "services-non-updatable-stubs.sources",
+    srcs: [":services-non-updatable-sources"],
+    defaults: ["services-stubs-default"],
+    check_api: {
+        current: {
+            api_file: "api/non-updatable-current.txt",
+            removed_api_file: "api/non-updatable-removed.txt",
+        },
+        api_lint: {
+            enabled: true,
+            new_since: ":android-non-updatable.api.system-server.latest",
+            baseline_file: "api/non-updatable-lint-baseline.txt",
+        },
+    },
+    dists: [
+        {
+            targets: ["sdk", "win_sdk"],
+            dir: "apistubs/android/system-server/api",
+            dest: "android-non-updatable.txt",
+            tag: ".api.txt"
+        },
+        {
+            targets: ["sdk", "win_sdk"],
+            dir: "apistubs/android/system-server/api",
+            dest: "android-non-updatable-removed.txt",
+            tag: ".removed-api.txt",
+        },
+    ]
+}
\ No newline at end of file
diff --git a/services/api/non-updatable-current.txt b/services/api/non-updatable-current.txt
new file mode 100644
index 0000000..3c72d38
--- /dev/null
+++ b/services/api/non-updatable-current.txt
@@ -0,0 +1,55 @@
+// Signature format: 2.0
+package com.android.server {
+
+  public final class LocalManagerRegistry {
+    method public static <T> void addManager(@NonNull Class<T>, @NonNull T);
+    method @Nullable public static <T> T getManager(@NonNull Class<T>);
+  }
+
+  public abstract class SystemService {
+    ctor public SystemService(@NonNull android.content.Context);
+    method @NonNull public final android.content.Context getContext();
+    method public boolean isUserSupported(@NonNull com.android.server.SystemService.TargetUser);
+    method public void onBootPhase(int);
+    method public abstract void onStart();
+    method public void onUserStarting(@NonNull com.android.server.SystemService.TargetUser);
+    method public void onUserStopped(@NonNull com.android.server.SystemService.TargetUser);
+    method public void onUserStopping(@NonNull com.android.server.SystemService.TargetUser);
+    method public void onUserSwitching(@Nullable com.android.server.SystemService.TargetUser, @NonNull com.android.server.SystemService.TargetUser);
+    method public void onUserUnlocked(@NonNull com.android.server.SystemService.TargetUser);
+    method public void onUserUnlocking(@NonNull com.android.server.SystemService.TargetUser);
+    method protected final void publishBinderService(@NonNull String, @NonNull android.os.IBinder);
+    method protected final void publishBinderService(@NonNull String, @NonNull android.os.IBinder, boolean);
+    field public static final int PHASE_ACTIVITY_MANAGER_READY = 550; // 0x226
+    field public static final int PHASE_BOOT_COMPLETED = 1000; // 0x3e8
+    field public static final int PHASE_DEVICE_SPECIFIC_SERVICES_READY = 520; // 0x208
+    field public static final int PHASE_LOCK_SETTINGS_READY = 480; // 0x1e0
+    field public static final int PHASE_SYSTEM_SERVICES_READY = 500; // 0x1f4
+    field public static final int PHASE_THIRD_PARTY_APPS_CAN_START = 600; // 0x258
+    field public static final int PHASE_WAIT_FOR_DEFAULT_DISPLAY = 100; // 0x64
+  }
+
+  public static final class SystemService.TargetUser {
+    method @NonNull public android.os.UserHandle getUserHandle();
+  }
+
+}
+
+package com.android.server.role {
+
+  public interface RoleServicePlatformHelper {
+    method @NonNull public String computePackageStateHash(int);
+    method @NonNull public java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getLegacyRoleState(int);
+  }
+
+}
+
+package com.android.server.wifi {
+
+  public class SupplicantManager {
+    method public static void start();
+    method public static void stop();
+  }
+
+}
+
diff --git a/services/api/non-updatable-lint-baseline.txt b/services/api/non-updatable-lint-baseline.txt
new file mode 100644
index 0000000..b46d21e
--- /dev/null
+++ b/services/api/non-updatable-lint-baseline.txt
@@ -0,0 +1,9 @@
+// Baseline format: 1.0
+NotCloseable: com.android.server.wifi.SupplicantManager:
+    Classes that release resources (stop()) should implement AutoClosable and CloseGuard: class com.android.server.wifi.SupplicantManager
+
+
+ProtectedMember: com.android.server.SystemService#publishBinderService(String, android.os.IBinder):
+    Protected methods not allowed; must be public: method com.android.server.SystemService.publishBinderService(String,android.os.IBinder)}
+ProtectedMember: com.android.server.SystemService#publishBinderService(String, android.os.IBinder, boolean):
+    Protected methods not allowed; must be public: method com.android.server.SystemService.publishBinderService(String,android.os.IBinder,boolean)}
diff --git a/services/api/non-updatable-removed.txt b/services/api/non-updatable-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/services/api/non-updatable-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 10b00d3..76c8d30 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -257,6 +257,8 @@
         new PackageMonitor() {
             @Override
             public void onPackageRemoved(String packageName, int uid) {
+                Slog.d(LOG_TAG, "onPackageRemoved(packageName = " + packageName
+                        + ", uid = " + uid + ")");
                 int userId = getChangingUserId();
                 updateAssociations(
                         as -> CollectionUtils.filter(as,
@@ -268,6 +270,7 @@
 
             @Override
             public void onPackageModified(String packageName) {
+                Slog.d(LOG_TAG, "onPackageModified(packageName = " + packageName + ")");
                 int userId = getChangingUserId();
                 forEach(getAllAssociations(userId, packageName), association -> {
                     updateSpecialAccessPermissionForAssociatedPackage(association);
@@ -304,7 +307,7 @@
                         mBleStateBroadcastReceiver, mBleStateBroadcastReceiver.mIntentFilter);
                 initBleScanning();
             } else {
-                Log.w(LOG_TAG, "No BluetoothAdapter available");
+                Slog.w(LOG_TAG, "No BluetoothAdapter available");
             }
         }
     }
@@ -324,6 +327,7 @@
     }
 
     void maybeGrantAutoRevokeExemptions() {
+        Slog.d(LOG_TAG, "maybeGrantAutoRevokeExemptions()");
         PackageManager pm = getContext().getPackageManager();
         for (int userId : LocalServices.getService(UserManagerInternal.class).getUserIds()) {
             SharedPreferences pref = getContext().getSharedPreferences(
@@ -343,7 +347,7 @@
                         int uid = pm.getPackageUidAsUser(a.getPackageName(), userId);
                         exemptFromAutoRevoke(a.getPackageName(), uid);
                     } catch (PackageManager.NameNotFoundException e) {
-                        Log.w(LOG_TAG, "Unknown companion package: " + a.getPackageName(), e);
+                        Slog.w(LOG_TAG, "Unknown companion package: " + a.getPackageName(), e);
                     }
                 }
             } finally {
@@ -354,10 +358,13 @@
 
     @Override
     public void binderDied() {
+        Slog.w(LOG_TAG, "binderDied()");
         mMainHandler.post(this::cleanup);
     }
 
     private void cleanup() {
+        Slog.d(LOG_TAG, "cleanup(); discovery = "
+                + mOngoingDeviceDiscovery + ", request = " + mRequest);
         synchronized (mLock) {
             AndroidFuture<Association> ongoingDeviceDiscovery = mOngoingDeviceDiscovery;
             if (ongoingDeviceDiscovery != null && !ongoingDeviceDiscovery.isDone()) {
@@ -400,10 +407,8 @@
                 AssociationRequest request,
                 IFindDeviceCallback callback,
                 String callingPackage) throws RemoteException {
-            if (DEBUG) {
-                Slog.i(LOG_TAG, "associate(request = " + request + ", callback = " + callback
-                        + ", callingPackage = " + callingPackage + ")");
-            }
+            Slog.i(LOG_TAG, "associate(request = " + request + ", callback = " + callback
+                    + ", callingPackage = " + callingPackage + ")");
             checkNotNull(request, "Request cannot be null");
             checkNotNull(callback, "Callback cannot be null");
             checkCallerIsSystemOr(callingPackage);
@@ -423,9 +428,13 @@
                                     request.getDeviceProfile());
 
             mOngoingDeviceDiscovery = fetchProfileDescription.thenComposeAsync(description -> {
+                Slog.d(LOG_TAG, "fetchProfileDescription done: " + description);
+
                 request.setDeviceProfilePrivilegesDescription(description);
 
                 return mServiceConnectors.forUser(userId).postAsync(service -> {
+                    Slog.d(LOG_TAG, "Connected to CDM service; starting discovery for " + request);
+
                     AndroidFuture<Association> future = new AndroidFuture<>();
                     service.startDiscovery(request, callingPackage, callback, future);
                     return future;
@@ -438,7 +447,7 @@
                     if (err == null) {
                         addAssociation(association);
                     } else {
-                        Log.e(LOG_TAG, "Failed to discover device(s)", err);
+                        Slog.e(LOG_TAG, "Failed to discover device(s)", err);
                         callback.onFailure("No devices found: " + err.getMessage());
                     }
                     cleanup();
@@ -452,6 +461,7 @@
         public void stopScan(AssociationRequest request,
                 IFindDeviceCallback callback,
                 String callingPackage) {
+            Slog.d(LOG_TAG, "stopScan(request = " + request + ")");
             if (Objects.equals(request, mRequest)
                     && Objects.equals(callback, mFindDeviceCallback)
                     && Objects.equals(callingPackage, mCallingPackage)) {
@@ -712,7 +722,7 @@
                     getAllAssociations(association.getUserId()),
                     a -> !a.equals(association) && deviceProfile.equals(a.getDeviceProfile()));
             if (otherAssociationWithDeviceProfile != null) {
-                Log.i(LOG_TAG, "Not revoking " + deviceProfile
+                Slog.i(LOG_TAG, "Not revoking " + deviceProfile
                         + " for " + association
                         + " - profile still present in " + otherAssociationWithDeviceProfile);
             } else {
@@ -726,7 +736,7 @@
                             getContext().getMainExecutor(),
                             success -> {
                                 if (!success) {
-                                    Log.e(LOG_TAG, "Failed to revoke device profile role "
+                                    Slog.e(LOG_TAG, "Failed to revoke device profile role "
                                             + association.getDeviceProfile()
                                             + " to " + association.getPackageName()
                                             + " for user " + association.getUserId());
@@ -794,7 +804,7 @@
                     packageName,
                     AppOpsManager.MODE_IGNORED);
         } catch (RemoteException e) {
-            Log.w(LOG_TAG,
+            Slog.w(LOG_TAG,
                     "Error while granting auto revoke exemption for " + packageName, e);
         }
     }
@@ -819,9 +829,7 @@
     }
 
     private void recordAssociation(Association association) {
-        if (DEBUG) {
-            Log.i(LOG_TAG, "recordAssociation(" + association + ")");
-        }
+        Slog.i(LOG_TAG, "recordAssociation(" + association + ")");
         updateAssociations(associations -> CollectionUtils.add(associations, association));
     }
 
@@ -835,9 +843,7 @@
             final Set<Association> old = getAllAssociations(userId);
             Set<Association> associations = new ArraySet<>(old);
             associations = update.apply(associations);
-            if (DEBUG) {
-                Slog.i(LOG_TAG, "Updating associations: " + old + "  -->  " + associations);
-            }
+            Slog.i(LOG_TAG, "Updating associations: " + old + "  -->  " + associations);
             mCachedAssociations.put(userId, Collections.unmodifiableSet(associations));
             BackgroundThread.getHandler().sendMessage(PooledLambda.obtainMessage(
                     CompanionDeviceManagerService::persistAssociations,
@@ -866,9 +872,7 @@
     }
 
     private void persistAssociations(Set<Association> associations, int userId) {
-        if (DEBUG) {
-            Slog.i(LOG_TAG, "Writing associations to disk: " + associations);
-        }
+        Slog.i(LOG_TAG, "Writing associations to disk: " + associations);
         final AtomicFile file = getStorageFileForUser(userId);
         synchronized (file) {
             file.write(out -> {
@@ -919,9 +923,7 @@
             if (mCachedAssociations.get(userId) == null) {
                 mCachedAssociations.put(userId, Collections.unmodifiableSet(
                         emptyIfNull(readAllAssociations(userId))));
-                if (DEBUG) {
-                    Slog.i(LOG_TAG, "Read associations from disk: " + mCachedAssociations);
-                }
+                Slog.i(LOG_TAG, "Read associations from disk: " + mCachedAssociations);
             }
             return mCachedAssociations.get(userId);
         }
@@ -1002,13 +1004,15 @@
     }
 
     void onDeviceConnected(String address) {
+        Slog.d(LOG_TAG, "onDeviceConnected(address = " + address + ")");
+
         mCurrentlyConnectedDevices.add(address);
 
         for (UserInfo user : getAllUsers()) {
             for (Association association : getAllAssociations(user.id)) {
                 if (Objects.equals(address, association.getDeviceMacAddress())) {
                     if (association.getDeviceProfile() != null) {
-                        Log.i(LOG_TAG, "Granting role " + association.getDeviceProfile()
+                        Slog.i(LOG_TAG, "Granting role " + association.getDeviceProfile()
                                 + " to " + association.getPackageName()
                                 + " due to device connected: " + association.getDeviceMacAddress());
                         grantDeviceProfile(association);
@@ -1021,6 +1025,8 @@
     }
 
     private void grantDeviceProfile(Association association) {
+        Slog.i(LOG_TAG, "grantDeviceProfile(association = " + association + ")");
+
         if (association.getDeviceProfile() != null) {
             mRoleManager.addRoleHolderAsUser(
                     association.getDeviceProfile(),
@@ -1030,7 +1036,7 @@
                     getContext().getMainExecutor(),
                     success -> {
                         if (!success) {
-                            Log.e(LOG_TAG, "Failed to grant device profile role "
+                            Slog.e(LOG_TAG, "Failed to grant device profile role "
                                     + association.getDeviceProfile()
                                     + " to " + association.getPackageName()
                                     + " for user " + association.getUserId());
@@ -1040,6 +1046,8 @@
     }
 
     void onDeviceDisconnected(String address) {
+        Slog.d(LOG_TAG, "onDeviceConnected(address = " + address + ")");
+
         mCurrentlyConnectedDevices.remove(address);
 
         onDeviceDisappeared(address);
@@ -1059,13 +1067,13 @@
         List<ResolveInfo> packageResolveInfos = filter(resolveInfos,
                 info -> Objects.equals(info.serviceInfo.packageName, a.getPackageName()));
         if (packageResolveInfos.size() != 1) {
-            Log.w(LOG_TAG, "Device presence listener package must have exactly one "
+            Slog.w(LOG_TAG, "Device presence listener package must have exactly one "
                     + "CompanionDeviceService, but " + a.getPackageName()
                     + " has " + packageResolveInfos.size());
             return new ServiceConnector.NoOp<>();
         }
         ComponentName componentName = packageResolveInfos.get(0).serviceInfo.getComponentName();
-        Log.i(LOG_TAG, "Initializing CompanionDeviceService binding for " + componentName);
+        Slog.i(LOG_TAG, "Initializing CompanionDeviceService binding for " + componentName);
         return new ServiceConnector.Impl<>(getContext(),
                 new Intent(CompanionDeviceService.SERVICE_INTERFACE).setComponent(componentName),
                 BIND_IMPORTANT,
@@ -1077,7 +1085,7 @@
         @Override
         public void onScanResult(int callbackType, ScanResult result) {
             if (DEBUG) {
-                Log.i(LOG_TAG, "onScanResult(callbackType = "
+                Slog.i(LOG_TAG, "onScanResult(callbackType = "
                         + callbackType + ", result = " + result + ")");
             }
 
@@ -1096,9 +1104,9 @@
             if (errorCode == SCAN_FAILED_ALREADY_STARTED) {
                 // ignore - this might happen if BT tries to auto-restore scans for us in the
                 // future
-                Log.i(LOG_TAG, "Ignoring BLE scan error: SCAN_FAILED_ALREADY_STARTED");
+                Slog.i(LOG_TAG, "Ignoring BLE scan error: SCAN_FAILED_ALREADY_STARTED");
             } else {
-                Log.w(LOG_TAG, "Failed to start BLE scan: error " + errorCode);
+                Slog.w(LOG_TAG, "Failed to start BLE scan: error " + errorCode);
             }
         }
     }
@@ -1112,7 +1120,7 @@
         public void onReceive(Context context, Intent intent) {
             int previousState = intent.getIntExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, -1);
             int newState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
-            Log.i(LOG_TAG, "Received BT state transition broadcast: "
+            Slog.d(LOG_TAG, "Received BT state transition broadcast: "
                     + BluetoothAdapter.nameForState(previousState)
                     + " -> " + BluetoothAdapter.nameForState(newState));
 
@@ -1122,7 +1130,7 @@
                 if (mBluetoothAdapter.getBluetoothLeScanner() != null) {
                     startBleScan();
                 } else {
-                    Log.wtf(LOG_TAG, "BLE on, but BluetoothLeScanner == null");
+                    Slog.wtf(LOG_TAG, "BLE on, but BluetoothLeScanner == null");
                 }
             }
         }
@@ -1136,6 +1144,8 @@
 
         @Override
         public void run() {
+            Slog.i(LOG_TAG, "UnbindDeviceListenersRunnable.run(); devicesNearby = "
+                    + mDevicesLastNearby);
             int size = mDevicesLastNearby.size();
             for (int i = 0; i < size; i++) {
                 String address = mDevicesLastNearby.keyAt(i);
@@ -1162,12 +1172,15 @@
         }
 
         public void schedule() {
+            Slog.d(LOG_TAG,
+                    "TriggerDeviceDisappearedRunnable.schedule(address = " + mAddress + ")");
             mMainHandler.removeCallbacks(this);
             mMainHandler.postDelayed(this, this, DEVICE_DISAPPEARED_TIMEOUT_MS);
         }
 
         @Override
         public void run() {
+            Slog.d(LOG_TAG, "TriggerDeviceDisappearedRunnable.run(address = " + mAddress + ")");
             onDeviceDisappeared(mAddress);
         }
     }
@@ -1187,6 +1200,8 @@
     }
 
     private void onDeviceNearby(String address) {
+        Slog.i(LOG_TAG, "onDeviceNearby(address = " + address + ")");
+
         Date timestamp = new Date();
         Date oldTimestamp = mDevicesLastNearby.put(address, timestamp);
 
@@ -1203,7 +1218,7 @@
             for (Association association : getAllAssociations(address)) {
                 if (association.isNotifyOnDeviceNearby()) {
                     if (DEBUG) {
-                        Log.i(LOG_TAG, "Device " + address
+                        Slog.i(LOG_TAG, "Device " + address
                                 + " managed by " + association.getPackageName()
                                 + " is nearby on " + timestamp);
                     }
@@ -1215,11 +1230,13 @@
     }
 
     private void onDeviceDisappeared(String address) {
+        Slog.i(LOG_TAG, "onDeviceDisappeared(address = " + address + ")");
+
         boolean hasDeviceListeners = false;
         for (Association association : getAllAssociations(address)) {
             if (association.isNotifyOnDeviceNearby()) {
                 if (DEBUG) {
-                    Log.i(LOG_TAG, "Device " + address
+                    Slog.i(LOG_TAG, "Device " + address
                             + " managed by " + association.getPackageName()
                             + " disappeared; last seen on " + mDevicesLastNearby.get(address));
                 }
@@ -1245,19 +1262,19 @@
     }
 
     private void initBleScanning() {
-        Log.i(LOG_TAG, "initBleScanning()");
+        Slog.i(LOG_TAG, "initBleScanning()");
 
         boolean bluetoothReady = mBluetoothAdapter.registerServiceLifecycleCallback(
                 new BluetoothAdapter.ServiceLifecycleCallback() {
                     @Override
                     public void onBluetoothServiceUp() {
-                        Log.i(LOG_TAG, "Bluetooth stack is up");
+                        Slog.i(LOG_TAG, "Bluetooth stack is up");
                         startBleScan();
                     }
 
                     @Override
                     public void onBluetoothServiceDown() {
-                        Log.w(LOG_TAG, "Bluetooth stack is down");
+                        Slog.w(LOG_TAG, "Bluetooth stack is down");
                     }
                 });
         if (bluetoothReady) {
@@ -1266,7 +1283,7 @@
     }
 
     void startBleScan() {
-        Log.i(LOG_TAG, "startBleScan()");
+        Slog.i(LOG_TAG, "startBleScan()");
 
         List<ScanFilter> filters = getBleScanFilters();
         if (filters.isEmpty()) {
@@ -1274,7 +1291,7 @@
         }
         BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner();
         if (scanner == null) {
-            Log.w(LOG_TAG, "scanner == null (likely BLE isn't ON yet)");
+            Slog.w(LOG_TAG, "scanner == null (likely BLE isn't ON yet)");
         } else {
             scanner.startScan(
                     filters,
@@ -1321,7 +1338,7 @@
         try {
             return Long.parseLong(str);
         } catch (NumberFormatException e) {
-            Log.w(LOG_TAG, "Failed to parse", e);
+            Slog.w(LOG_TAG, "Failed to parse", e);
             return def;
         }
     }
@@ -1380,7 +1397,7 @@
                 }
                 return 0;
             } catch (Throwable t) {
-                Log.e(LOG_TAG, "Error running a command: $ " + cmd, t);
+                Slog.e(LOG_TAG, "Error running a command: $ " + cmd, t);
                 getErrPrintWriter().println(Log.getStackTraceString(t));
                 return 1;
             }
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 154e183..5077cc6 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -3721,7 +3721,12 @@
         // Looking up the app passed param request in mRequests isn't possible since it may return
         // null for a request managed by a per-app default. Therefore use getNriForAppRequest() to
         // do the lookup since that will also find per-app default managed requests.
-        final NetworkRequestInfo nri = getNriForAppRequest(request);
+        // Additionally, this lookup needs to be relatively fast (hence the lookup optimization)
+        // to avoid potential race conditions when validating a package->uid mapping when sending
+        // the callback on the very low-chance that an application shuts down prior to the callback
+        // being sent.
+        final NetworkRequestInfo nri = mNetworkRequests.get(request) != null
+                ? mNetworkRequests.get(request) : getNriForAppRequest(request);
 
         if (nri != null) {
             if (Process.SYSTEM_UID != callingUid && Process.NETWORK_STACK_UID != callingUid
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index 27210da..329ab99 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -22,6 +22,7 @@
 import static java.util.Objects.requireNonNull;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.net.ConnectivityManager;
@@ -29,8 +30,10 @@
 import android.net.NetworkCapabilities;
 import android.net.TelephonyNetworkSpecifier;
 import android.net.vcn.IVcnManagementService;
+import android.net.vcn.IVcnStatusCallback;
 import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
 import android.net.vcn.VcnConfig;
+import android.net.vcn.VcnManager.VcnErrorCode;
 import android.net.vcn.VcnUnderlyingNetworkPolicy;
 import android.net.wifi.WifiInfo;
 import android.os.Binder;
@@ -54,6 +57,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.internal.util.LocationPermissionChecker;
 import com.android.server.vcn.TelephonySubscriptionTracker;
 import com.android.server.vcn.Vcn;
 import com.android.server.vcn.VcnContext;
@@ -124,6 +128,7 @@
  *
  * @hide
  */
+// TODO(b/180451994): ensure all incoming + outgoing calls have a cleared calling identity
 public class VcnManagementService extends IVcnManagementService.Stub {
     @NonNull private static final String TAG = VcnManagementService.class.getSimpleName();
 
@@ -147,6 +152,9 @@
     @NonNull private final TelephonySubscriptionTracker mTelephonySubscriptionTracker;
     @NonNull private final VcnContext mVcnContext;
 
+    /** Can only be assigned when {@link #systemReady()} is called, since it uses AppOpsManager. */
+    @Nullable private LocationPermissionChecker mLocationPermissionChecker;
+
     @GuardedBy("mLock")
     @NonNull
     private final Map<ParcelUuid, VcnConfig> mConfigs = new ArrayMap<>();
@@ -169,6 +177,10 @@
     private final Map<IBinder, PolicyListenerBinderDeath> mRegisteredPolicyListeners =
             new ArrayMap<>();
 
+    @GuardedBy("mLock")
+    @NonNull
+    private final Map<IBinder, VcnStatusCallbackInfo> mRegisteredStatusCallbacks = new ArrayMap<>();
+
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     VcnManagementService(@NonNull Context context, @NonNull Dependencies deps) {
         mContext = requireNonNull(context, "Missing context");
@@ -293,8 +305,8 @@
                 @NonNull ParcelUuid subscriptionGroup,
                 @NonNull VcnConfig config,
                 @NonNull TelephonySubscriptionSnapshot snapshot,
-                @NonNull VcnSafemodeCallback safemodeCallback) {
-            return new Vcn(vcnContext, subscriptionGroup, config, snapshot, safemodeCallback);
+                @NonNull VcnCallback vcnCallback) {
+            return new Vcn(vcnContext, subscriptionGroup, config, snapshot, vcnCallback);
         }
 
         /** Gets the subId indicated by the given {@link WifiInfo}. */
@@ -302,6 +314,11 @@
             // TODO(b/178501049): use the subId indicated by WifiInfo#getSubscriptionId
             return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
         }
+
+        /** Creates a new LocationPermissionChecker for the provided Context. */
+        public LocationPermissionChecker newLocationPermissionChecker(@NonNull Context context) {
+            return new LocationPermissionChecker(context);
+        }
     }
 
     /** Notifies the VcnManagementService that external dependencies can be set up. */
@@ -309,6 +326,7 @@
         mContext.getSystemService(ConnectivityManager.class)
                 .registerNetworkProvider(mNetworkProvider);
         mTelephonySubscriptionTracker.register();
+        mLocationPermissionChecker = mDeps.newLocationPermissionChecker(mVcnContext.getContext());
     }
 
     private void enforcePrimaryUser() {
@@ -440,12 +458,10 @@
         // TODO(b/176939047): Support multiple VCNs active at the same time, or limit to one active
         //                    VCN.
 
-        final VcnSafemodeCallbackImpl safemodeCallback =
-                new VcnSafemodeCallbackImpl(subscriptionGroup);
+        final VcnCallbackImpl vcnCallback = new VcnCallbackImpl(subscriptionGroup);
 
         final Vcn newInstance =
-                mDeps.newVcn(
-                        mVcnContext, subscriptionGroup, config, mLastSnapshot, safemodeCallback);
+                mDeps.newVcn(mVcnContext, subscriptionGroup, config, mLastSnapshot, vcnCallback);
         mVcns.put(subscriptionGroup, newInstance);
 
         // Now that a new VCN has started, notify all registered listeners to refresh their
@@ -551,6 +567,14 @@
         }
     }
 
+    /** Get current VcnStatusCallbacks for testing purposes. */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public Map<IBinder, VcnStatusCallbackInfo> getAllStatusCallbacks() {
+        synchronized (mLock) {
+            return Collections.unmodifiableMap(mRegisteredStatusCallbacks);
+        }
+    }
+
     /** Binder death recipient used to remove a registered policy listener. */
     private class PolicyListenerBinderDeath implements Binder.DeathRecipient {
         @NonNull private final IVcnUnderlyingNetworkPolicyListener mListener;
@@ -672,22 +696,136 @@
         return new VcnUnderlyingNetworkPolicy(false /* isTearDownRequested */, networkCapabilities);
     }
 
-    /** Callback for signalling when a Vcn has entered Safemode. */
-    public interface VcnSafemodeCallback {
-        /** Called by a Vcn to signal that it has entered Safemode. */
-        void onEnteredSafemode();
-    }
+    /** Binder death recipient used to remove registered VcnStatusCallbacks. */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    class VcnStatusCallbackInfo implements Binder.DeathRecipient {
+        @NonNull final ParcelUuid mSubGroup;
+        @NonNull final IVcnStatusCallback mCallback;
+        @NonNull final String mPkgName;
+        final int mUid;
 
-    /** VcnSafemodeCallback is used by Vcns to notify VcnManagementService on entering Safemode. */
-    private class VcnSafemodeCallbackImpl implements VcnSafemodeCallback {
-        @NonNull private final ParcelUuid mSubGroup;
-
-        private VcnSafemodeCallbackImpl(@NonNull final ParcelUuid subGroup) {
-            mSubGroup = Objects.requireNonNull(subGroup, "Missing subGroup");
+        private VcnStatusCallbackInfo(
+                @NonNull ParcelUuid subGroup,
+                @NonNull IVcnStatusCallback callback,
+                @NonNull String pkgName,
+                int uid) {
+            mSubGroup = subGroup;
+            mCallback = callback;
+            mPkgName = pkgName;
+            mUid = uid;
         }
 
         @Override
-        public void onEnteredSafemode() {
+        public void binderDied() {
+            Log.e(TAG, "app died without unregistering VcnStatusCallback");
+            unregisterVcnStatusCallback(mCallback);
+        }
+    }
+
+    /** Registers the provided callback for receiving VCN status updates. */
+    @Override
+    public void registerVcnStatusCallback(
+            @NonNull ParcelUuid subGroup,
+            @NonNull IVcnStatusCallback callback,
+            @NonNull String opPkgName) {
+        final int callingUid = mDeps.getBinderCallingUid();
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            requireNonNull(subGroup, "subGroup must not be null");
+            requireNonNull(callback, "callback must not be null");
+            requireNonNull(opPkgName, "opPkgName must not be null");
+
+            mContext.getSystemService(AppOpsManager.class).checkPackage(callingUid, opPkgName);
+
+            final IBinder cbBinder = callback.asBinder();
+            final VcnStatusCallbackInfo cbInfo =
+                    new VcnStatusCallbackInfo(
+                            subGroup, callback, opPkgName, mDeps.getBinderCallingUid());
+
+            try {
+                cbBinder.linkToDeath(cbInfo, 0 /* flags */);
+            } catch (RemoteException e) {
+                // Remote binder already died - don't add to mRegisteredStatusCallbacks and exit
+                return;
+            }
+
+            synchronized (mLock) {
+                if (mRegisteredStatusCallbacks.containsKey(cbBinder)) {
+                    throw new IllegalStateException(
+                            "Attempting to register a callback that is already in use");
+                }
+
+                mRegisteredStatusCallbacks.put(cbBinder, cbInfo);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /** Unregisters the provided callback from receiving future VCN status updates. */
+    @Override
+    public void unregisterVcnStatusCallback(@NonNull IVcnStatusCallback callback) {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            requireNonNull(callback, "callback must not be null");
+
+            final IBinder cbBinder = callback.asBinder();
+            synchronized (mLock) {
+                VcnStatusCallbackInfo cbInfo = mRegisteredStatusCallbacks.remove(cbBinder);
+
+                if (cbInfo != null) {
+                    cbBinder.unlinkToDeath(cbInfo, 0 /* flags */);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    // TODO(b/180452282): Make name more generic and implement directly with VcnManagementService
+    /** Callback for Vcn signals sent up to VcnManagementService. */
+    public interface VcnCallback {
+        /** Called by a Vcn to signal that it has entered safe mode. */
+        void onEnteredSafeMode();
+
+        /** Called by a Vcn to signal that an error occurred. */
+        void onGatewayConnectionError(
+                @NonNull int[] networkCapabilities,
+                @VcnErrorCode int errorCode,
+                @Nullable String exceptionClass,
+                @Nullable String exceptionMessage);
+    }
+
+    /** VcnCallbackImpl for Vcn signals sent up to VcnManagementService. */
+    private class VcnCallbackImpl implements VcnCallback {
+        @NonNull private final ParcelUuid mSubGroup;
+
+        private VcnCallbackImpl(@NonNull final ParcelUuid subGroup) {
+            mSubGroup = Objects.requireNonNull(subGroup, "Missing subGroup");
+        }
+
+        private boolean isCallbackPermissioned(@NonNull VcnStatusCallbackInfo cbInfo) {
+            if (!mSubGroup.equals(cbInfo.mSubGroup)) {
+                return false;
+            }
+
+            if (!mLastSnapshot.packageHasPermissionsForSubscriptionGroup(
+                    mSubGroup, cbInfo.mPkgName)) {
+                return false;
+            }
+
+            if (!mLocationPermissionChecker.checkLocationPermission(
+                    cbInfo.mPkgName,
+                    "VcnStatusCallback" /* featureId */,
+                    cbInfo.mUid,
+                    null /* message */)) {
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        public void onEnteredSafeMode() {
             synchronized (mLock) {
                 // Ignore if this subscription group doesn't exist anymore
                 if (!mVcns.containsKey(mSubGroup)) {
@@ -695,6 +833,40 @@
                 }
 
                 notifyAllPolicyListenersLocked();
+
+                // Notify all registered StatusCallbacks for this subGroup
+                for (VcnStatusCallbackInfo cbInfo : mRegisteredStatusCallbacks.values()) {
+                    if (isCallbackPermissioned(cbInfo)) {
+                        Binder.withCleanCallingIdentity(() -> cbInfo.mCallback.onEnteredSafeMode());
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void onGatewayConnectionError(
+                @NonNull int[] networkCapabilities,
+                @VcnErrorCode int errorCode,
+                @Nullable String exceptionClass,
+                @Nullable String exceptionMessage) {
+            synchronized (mLock) {
+                // Ignore if this subscription group doesn't exist anymore
+                if (!mVcns.containsKey(mSubGroup)) {
+                    return;
+                }
+
+                // Notify all registered StatusCallbacks for this subGroup
+                for (VcnStatusCallbackInfo cbInfo : mRegisteredStatusCallbacks.values()) {
+                    if (isCallbackPermissioned(cbInfo)) {
+                        Binder.withCleanCallingIdentity(
+                                () ->
+                                        cbInfo.mCallback.onGatewayConnectionError(
+                                                networkCapabilities,
+                                                errorCode,
+                                                exceptionClass,
+                                                exceptionMessage));
+                    }
+                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 0b1c115..5ee0e04 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -130,6 +130,7 @@
 import static com.android.server.wm.ActivityTaskManagerService.DUMP_RECENTS_CMD;
 import static com.android.server.wm.ActivityTaskManagerService.DUMP_RECENTS_SHORT_CMD;
 import static com.android.server.wm.ActivityTaskManagerService.DUMP_STARTER_CMD;
+import static com.android.server.wm.ActivityTaskManagerService.DUMP_TOP_RESUMED_ACTIVITY;
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
 import static com.android.server.wm.ActivityTaskManagerService.relaunchReasonToString;
 
@@ -353,6 +354,7 @@
 import com.android.server.firewall.IntentFirewall;
 import com.android.server.graphics.fonts.FontManagerInternal;
 import com.android.server.job.JobSchedulerInternal;
+import com.android.server.os.NativeTombstoneManager;
 import com.android.server.pm.Installer;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 import com.android.server.uri.GrantUri;
@@ -8273,6 +8275,9 @@
         mUserController.handleIncomingUser(callingPid, callingUid, userId, true, ALLOW_NON_FULL,
                 "getHistoricalProcessExitReasons", null);
 
+        NativeTombstoneManager tombstoneService = LocalServices.getService(
+                NativeTombstoneManager.class);
+
         final ArrayList<ApplicationExitInfo> results = new ArrayList<ApplicationExitInfo>();
         if (!TextUtils.isEmpty(packageName)) {
             final int uid = enforceDumpPermissionForPackage(packageName, userId, callingUid,
@@ -8280,11 +8285,13 @@
             if (uid != Process.INVALID_UID) {
                 mProcessList.mAppExitInfoTracker.getExitInfo(
                         packageName, uid, pid, maxNum, results);
+                tombstoneService.collectTombstones(results, uid, pid, maxNum);
             }
         } else {
             // If no package name is given, use the caller's uid as the filter uid.
             mProcessList.mAppExitInfoTracker.getExitInfo(
                     packageName, callingUid, pid, maxNum, results);
+            tombstoneService.collectTombstones(results, callingUid, pid, maxNum);
         }
 
         return new ParceledListSlice<ApplicationExitInfo>(results);
@@ -8668,7 +8675,8 @@
             if (DUMP_ACTIVITIES_CMD.equals(cmd) || DUMP_ACTIVITIES_SHORT_CMD.equals(cmd)
                     || DUMP_LASTANR_CMD.equals(cmd) || DUMP_LASTANR_TRACES_CMD.equals(cmd)
                     || DUMP_STARTER_CMD.equals(cmd) || DUMP_CONTAINERS_CMD.equals(cmd)
-                    || DUMP_RECENTS_CMD.equals(cmd) || DUMP_RECENTS_SHORT_CMD.equals(cmd)) {
+                    || DUMP_RECENTS_CMD.equals(cmd) || DUMP_RECENTS_SHORT_CMD.equals(cmd)
+                    || DUMP_TOP_RESUMED_ACTIVITY.equals(cmd)) {
                 mAtmInternal.dump(
                         cmd, fd, pw, args, opti, true /* dumpAll */, dumpClient, dumpPackage);
             } else if ("binder-proxies".equals(cmd)) {
diff --git a/services/core/java/com/android/server/am/AppExitInfoTracker.java b/services/core/java/com/android/server/am/AppExitInfoTracker.java
index 17be210..b85d729 100644
--- a/services/core/java/com/android/server/am/AppExitInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppExitInfoTracker.java
@@ -63,8 +63,10 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.IoThread;
+import com.android.server.LocalServices;
 import com.android.server.ServiceThread;
 import com.android.server.SystemServiceManager;
+import com.android.server.os.NativeTombstoneManager;
 
 import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
@@ -78,6 +80,7 @@
 import java.util.Collections;
 import java.util.Date;
 import java.util.List;
+import java.util.Optional;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.BiConsumer;
@@ -762,6 +765,10 @@
      * Helper function for shell command
      */
     void clearHistoryProcessExitInfo(String packageName, int userId) {
+        NativeTombstoneManager tombstoneService = LocalServices.getService(
+                NativeTombstoneManager.class);
+        Optional<Integer> appId = Optional.empty();
+
         if (TextUtils.isEmpty(packageName)) {
             synchronized (mLock) {
                 removeByUserIdLocked(userId);
@@ -769,10 +776,13 @@
         } else {
             final int uid = mService.mPackageManagerInt.getPackageUid(packageName,
                     PackageManager.MATCH_ALL, userId);
+            appId = Optional.of(UserHandle.getAppId(uid));
             synchronized (mLock) {
                 removePackageLocked(packageName, uid, true, userId);
             }
         }
+
+        tombstoneService.purge(Optional.of(userId), appId);
         schedulePersistProcessExitInfo(true);
     }
 
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index 52bb55f..fc28bfb 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -50,8 +50,6 @@
 
 import libcore.util.EmptyArray;
 
-import java.util.ArrayList;
-import java.util.List;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
@@ -149,7 +147,6 @@
      * Maps an {@link EnergyConsumerType} to it's corresponding {@link EnergyConsumer#id}s,
      * unless it is of {@link EnergyConsumer#type}=={@link EnergyConsumerType#OTHER}
      */
-    // TODO(b/180029015): Hook this up (it isn't used yet)
     @GuardedBy("mWorkerLock")
     private @Nullable SparseArray<int[]> mEnergyConsumerTypeToIdMap = null;
 
@@ -209,11 +206,22 @@
                         = populateEnergyConsumerSubsystemMapsLocked();
                 if (idToConsumer != null) {
                     mMeasuredEnergySnapshot = new MeasuredEnergySnapshot(idToConsumer);
-                    final EnergyConsumerResult[] initialEcrs = getEnergyConsumptionData();
-                    // According to spec, initialEcrs will include 0s for consumers that haven't
-                    // used any energy yet, as long as they are supported; however, attributed uid
-                    // energies will be absent if their energy is 0.
-                    mMeasuredEnergySnapshot.updateAndGetDelta(initialEcrs);
+                    try {
+                        final EnergyConsumerResult[] initialEcrs = getEnergyConsumptionData().get(
+                                EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+                        // According to spec, initialEcrs will include 0s for consumers that haven't
+                        // used any energy yet, as long as they are supported; however,
+                        // attributed uid energies will be absent if their energy is 0.
+                        mMeasuredEnergySnapshot.updateAndGetDelta(initialEcrs);
+                    } catch (TimeoutException | InterruptedException e) {
+                        Slog.w(TAG, "timeout or interrupt reading initial getEnergyConsumedAsync: "
+                                + e);
+                        // Continue running, later attempts to query may be successful.
+                    } catch (ExecutionException e) {
+                        Slog.wtf(TAG, "exception reading initial getEnergyConsumedAsync: "
+                                + e.getCause());
+                        // Continue running, later attempts to query may be successful.
+                    }
                     numCustomBuckets = mMeasuredEnergySnapshot.getNumOtherOrdinals();
                     supportedStdBuckets = getSupportedEnergyBuckets(idToConsumer);
                 }
@@ -498,6 +506,8 @@
         CompletableFuture<ModemActivityInfo> modemFuture = CompletableFuture.completedFuture(null);
         boolean railUpdated = false;
 
+        CompletableFuture<EnergyConsumerResult[]> futureECRs = getMeasuredEnergyLocked(updateFlags);
+
         if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_WIFI) != 0) {
             // We were asked to fetch WiFi data.
             // Only fetch WiFi power data if it is supported.
@@ -574,9 +584,23 @@
             Slog.w(TAG, "exception reading modem stats: " + e.getCause());
         }
 
-        final MeasuredEnergySnapshot.MeasuredEnergyDeltaData measuredEnergyDeltas =
-                mMeasuredEnergySnapshot == null ? null :
-                mMeasuredEnergySnapshot.updateAndGetDelta(getMeasuredEnergyLocked(updateFlags));
+        final MeasuredEnergySnapshot.MeasuredEnergyDeltaData measuredEnergyDeltas;
+        if (mMeasuredEnergySnapshot == null || futureECRs == null) {
+            measuredEnergyDeltas = null;
+        } else {
+            EnergyConsumerResult[] ecrs;
+            try {
+                ecrs = futureECRs.get(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+            } catch (TimeoutException | InterruptedException e) {
+                // TODO (b/180519623): Invalidate the MeasuredEnergy derived data until next reset.
+                Slog.w(TAG, "timeout or interrupt reading getEnergyConsumedAsync: " + e);
+                ecrs = null;
+            } catch (ExecutionException e) {
+                Slog.wtf(TAG, "exception reading getEnergyConsumedAsync: " + e.getCause());
+                ecrs = null;
+            }
+            measuredEnergyDeltas = mMeasuredEnergySnapshot.updateAndGetDelta(ecrs);
+        }
 
         final long elapsedRealtime = SystemClock.elapsedRealtime();
         final long uptime = SystemClock.uptimeMillis();
@@ -786,22 +810,29 @@
         return buckets;
     }
 
-    /** Get {@link EnergyConsumerResult}s with the latest energy usage since boot. */
+    /** Get all {@link EnergyConsumerResult}s with the latest energy usage since boot. */
     @GuardedBy("mWorkerLock")
-    private @Nullable EnergyConsumerResult[] getEnergyConsumptionData() {
-        try {
-            return mPowerStatsInternal.getEnergyConsumedAsync(new int[0])
-                    .get(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
-        } catch (Exception e) {
-            Slog.e(TAG, "Failed to getEnergyConsumedAsync", e);
-            return null;
-        }
+    @Nullable
+    private CompletableFuture<EnergyConsumerResult[]> getEnergyConsumptionData() {
+        return getEnergyConsumptionData(new int[0]);
+    }
+
+    /**
+     * Get {@link EnergyConsumerResult}s of the specified {@link EnergyConsumer} ids with the latest
+     * energy usage since boot.
+     */
+    @GuardedBy("mWorkerLock")
+    @Nullable
+    private CompletableFuture<EnergyConsumerResult[]> getEnergyConsumptionData(int[] consumerIds) {
+        return mPowerStatsInternal.getEnergyConsumedAsync(consumerIds);
     }
 
     /** Fetch EnergyConsumerResult[] for supported subsystems based on the given updateFlags. */
+    @VisibleForTesting
     @GuardedBy("mWorkerLock")
-    private @Nullable EnergyConsumerResult[] getMeasuredEnergyLocked(@ExternalUpdateFlag int flags)
-    {
+    @Nullable
+    public CompletableFuture<EnergyConsumerResult[]> getMeasuredEnergyLocked(
+            @ExternalUpdateFlag int flags) {
         if (mMeasuredEnergySnapshot == null || mPowerStatsInternal == null) return null;
 
         if (flags == UPDATE_ALL) {
@@ -809,24 +840,27 @@
             return getEnergyConsumptionData();
         }
 
-        final List<Integer> energyConsumerIds = new ArrayList<>();
+        final IntArray energyConsumerIds = new IntArray();
+        if ((flags & UPDATE_CPU) != 0) {
+            addEnergyConsumerIdLocked(energyConsumerIds, EnergyConsumerType.CPU_CLUSTER);
+        }
         if ((flags & UPDATE_DISPLAY) != 0) {
             addEnergyConsumerIdLocked(energyConsumerIds, EnergyConsumerType.DISPLAY);
         }
         // TODO: Wifi, Bluetooth, etc., go here
 
-        if (energyConsumerIds.isEmpty()) {
+        if (energyConsumerIds.size() == 0) {
             return null;
         }
-        // TODO(b/180029015): Query specific subsystems from HAL based on energyConsumerIds.toArray
-        return getEnergyConsumptionData();
+        return getEnergyConsumptionData(energyConsumerIds.toArray());
     }
 
     @GuardedBy("mWorkerLock")
     private void addEnergyConsumerIdLocked(
-            List<Integer> energyConsumerIds, @EnergyConsumerType int type) {
-        final int consumerId = 0; // TODO(b/180029015): Use mEnergyConsumerTypeToIdMap to get this
-        energyConsumerIds.add(consumerId);
+            IntArray energyConsumerIds, @EnergyConsumerType int type) {
+        final int[] consumerIds = mEnergyConsumerTypeToIdMap.get(type);
+        if (consumerIds == null) return;
+        energyConsumerIds.addAll(consumerIds);
     }
 
     /** Populates the cached type->ids map, and returns the (inverse) id->EnergyConsumer map. */
@@ -840,12 +874,10 @@
             return null;
         }
 
-        // TODO(b/180029015): Initialize typeToIds
-        // Maps type -> {ids} (1:n map, since multiple ids might have the same type)
-        // final SparseArray<SparseIntArray> typeToIds = new SparseArray<>();
-
         // Maps id -> EnergyConsumer (1:1 map)
         final SparseArray<EnergyConsumer> idToConsumer = new SparseArray<>(energyConsumers.length);
+        // Maps type -> {ids} (1:n map, since multiple ids might have the same type)
+        final SparseArray<IntArray> tempTypeToId = new SparseArray<>();
 
         // Add all expected EnergyConsumers to the maps
         for (final EnergyConsumer consumer : energyConsumers) {
@@ -862,9 +894,23 @@
                 }
             }
             idToConsumer.put(consumer.id, consumer);
-            // TODO(b/180029015): Also populate typeToIds map
+
+            IntArray ids = tempTypeToId.get(consumer.type);
+            if (ids == null) {
+                ids = new IntArray();
+                tempTypeToId.put(consumer.type, ids);
+            }
+            ids.add(consumer.id);
         }
-        // TODO(b/180029015): Store typeToIds in mEnergyConsumerTypeToIdMap.
+
+        mEnergyConsumerTypeToIdMap = new SparseArray<>(tempTypeToId.size());
+        // Populate mEnergyConsumerTypeToIdMap with EnergyConsumer type to ids mappings
+        final int size = tempTypeToId.size();
+        for (int i = 0; i < size; i++) {
+            final int consumerType = tempTypeToId.keyAt(i);
+            final int[] consumerIds = tempTypeToId.valueAt(i).toArray();
+            mEnergyConsumerTypeToIdMap.put(consumerType, consumerIds);
+        }
         return idToConsumer;
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index b15a886..e19745e 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -40,6 +40,7 @@
 import android.hardware.biometrics.IBiometricServiceReceiver;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.biometrics.SensorPropertiesInternal;
 import android.hardware.face.IFaceService;
@@ -144,13 +145,14 @@
 
     private final class AuthServiceImpl extends IAuthService.Stub {
         @Override
-        public ITestSession createTestSession(int sensorId, @NonNull String opPackageName)
-                throws RemoteException {
+        public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
+                @NonNull String opPackageName) throws RemoteException {
             Utils.checkPermission(getContext(), TEST_BIOMETRIC);
 
             final long identity = Binder.clearCallingIdentity();
             try {
-                return mInjector.getBiometricService().createTestSession(sensorId, opPackageName);
+                return mInjector.getBiometricService()
+                        .createTestSession(sensorId, callback, opPackageName);
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 614c5f1..00a4e43 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -44,6 +44,7 @@
 import android.hardware.biometrics.IBiometricSysuiReceiver;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.biometrics.SensorPropertiesInternal;
 import android.hardware.fingerprint.FingerprintManager;
@@ -570,13 +571,13 @@
      */
     private final class BiometricServiceWrapper extends IBiometricService.Stub {
         @Override // Binder call
-        public ITestSession createTestSession(int sensorId, @NonNull String opPackageName)
-                throws RemoteException {
+        public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
+                @NonNull String opPackageName) throws RemoteException {
             checkInternalPermission();
 
             for (BiometricSensor sensor : mSensors) {
                 if (sensor.id == sensorId) {
-                    return sensor.impl.createTestSession(opPackageName);
+                    return sensor.impl.createTestSession(callback, opPackageName);
                 }
             }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
index e062695..16f82af 100644
--- a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
@@ -37,18 +37,16 @@
 
     private static final String TAG = "Biometrics/RemovalClient";
 
-    protected final int mBiometricId;
     private final BiometricUtils<S> mBiometricUtils;
     private final Map<Integer, Long> mAuthenticatorIds;
 
     public RemovalClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
-            int biometricId, int userId, @NonNull String owner, @NonNull BiometricUtils<S> utils,
-            int sensorId, @NonNull Map<Integer, Long> authenticatorIds, int statsModality) {
+            int userId, @NonNull String owner, @NonNull BiometricUtils<S> utils, int sensorId,
+            @NonNull Map<Integer, Long> authenticatorIds, int statsModality) {
         super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
                 statsModality, BiometricsProtoEnums.ACTION_REMOVE,
                 BiometricsProtoEnums.CLIENT_UNKNOWN);
-        mBiometricId = biometricId;
         mBiometricUtils = utils;
         mAuthenticatorIds = authenticatorIds;
     }
@@ -68,6 +66,7 @@
 
     @Override
     public void onRemoved(@Nullable BiometricAuthenticator.Identifier identifier, int remaining) {
+        Slog.d(TAG, "onRemoved: " + identifier.getBiometricId() + " remaining: " + remaining);
         if (identifier != null) {
             mBiometricUtils.removeBiometricForUser(getContext(), getTargetUserId(),
                     identifier.getBiometricId());
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
index f37cf18..06b049b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
@@ -21,6 +21,7 @@
 import android.hardware.biometrics.IBiometricSensorReceiver;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.SensorPropertiesInternal;
 import android.hardware.face.IFaceService;
 import android.os.IBinder;
@@ -41,8 +42,9 @@
     }
 
     @Override
-    public ITestSession createTestSession(@NonNull String opPackageName) throws RemoteException {
-        return mFaceService.createTestSession(mSensorId, opPackageName);
+    public ITestSession createTestSession(@NonNull ITestSessionCallback callback,
+            @NonNull String opPackageName) throws RemoteException {
+        return mFaceService.createTestSession(mSensorId, callback, opPackageName);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 8253927..6dbd590 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -31,6 +31,7 @@
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.face.IFace;
 import android.hardware.biometrics.face.SensorProps;
 import android.hardware.face.Face;
@@ -133,7 +134,8 @@
      */
     private final class FaceServiceWrapper extends IFaceService.Stub {
         @Override
-        public ITestSession createTestSession(int sensorId, @NonNull String opPackageName) {
+        public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
+                @NonNull String opPackageName) {
             Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
 
             final ServiceProvider provider = getProviderForSensor(sensorId);
@@ -143,7 +145,7 @@
                 return null;
             }
 
-            return provider.createTestSession(sensorId, opPackageName);
+            return provider.createTestSession(sensorId, callback, opPackageName);
         }
 
         @Override
@@ -386,7 +388,22 @@
                     opPackageName);
         }
 
-        @Override
+        @Override // Binder call
+        public void removeAll(final IBinder token, final int userId,
+                final IFaceServiceReceiver receiver, final String opPackageName) {
+            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+
+            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+            if (provider == null) {
+                Slog.w(TAG, "Null provider for removeAll");
+                return;
+            }
+
+            provider.second.scheduleRemoveAll(provider.first, token, userId, receiver,
+                    opPackageName);
+        }
+
+        @Override // Binder call
         public void addLockoutResetCallback(final IBiometricServiceLockoutResetCallback callback,
                 final String opPackageName) {
             Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
index cc24b89..88edfbf 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.face.Face;
 import android.hardware.face.FaceManager;
 import android.hardware.face.FaceSensorPropertiesInternal;
@@ -28,6 +29,7 @@
 import android.os.NativeHandle;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.LockoutTracker;
 
@@ -109,6 +111,9 @@
     void scheduleRemove(int sensorId, @NonNull IBinder token, int faceId, int userId,
             @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName);
 
+    void scheduleRemoveAll(int sensorId, @NonNull IBinder token, int userId,
+            @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName);
+
     void scheduleResetLockout(int sensorId, int userId, @NonNull byte[] hardwareAuthToken);
 
     void scheduleSetFeature(int sensorId, @NonNull IBinder token, int userId, int feature,
@@ -120,7 +125,8 @@
 
     void startPreparedClient(int sensorId, int cookie);
 
-    void scheduleInternalCleanup(int sensorId, int userId);
+    void scheduleInternalCleanup(int sensorId, int userId,
+            @Nullable BaseClientMonitor.Callback callback);
 
     void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
             boolean clearSchedulerBuffer);
@@ -130,7 +136,8 @@
     void dumpInternal(int sensorId, @NonNull PrintWriter pw);
 
     @NonNull
-    ITestSession createTestSession(int sensorId, @NonNull String opPackageName);
+    ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
+            @NonNull String opPackageName);
 
     void dumpHal(int sensorId, @NonNull FileDescriptor fd, @NonNull String[] args);
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
index 897ebd7..a5e6ddb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.face.AuthenticationFrame;
 import android.hardware.biometrics.face.BaseFrame;
 import android.hardware.face.Face;
@@ -28,10 +29,12 @@
 import android.hardware.face.FaceEnrollFrame;
 import android.hardware.face.IFaceServiceReceiver;
 import android.os.Binder;
+import android.os.RemoteException;
 import android.util.Slog;
 
 import com.android.server.biometrics.HardwareAuthTokenUtils;
 import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.face.FaceUtils;
 
 import java.util.HashSet;
@@ -49,6 +52,7 @@
 
     @NonNull private final Context mContext;
     private final int mSensorId;
+    @NonNull private final ITestSessionCallback mCallback;
     @NonNull private final FaceProvider mProvider;
     @NonNull private final Sensor mSensor;
     @NonNull private final Set<Integer> mEnrollmentIds;
@@ -132,9 +136,11 @@
     };
 
     BiometricTestSessionImpl(@NonNull Context context, int sensorId,
+            @NonNull ITestSessionCallback callback,
             @NonNull FaceProvider provider, @NonNull Sensor sensor) {
         mContext = context;
         mSensorId = sensorId;
+        mCallback = callback;
         mProvider = provider;
         mSensor = sensor;
         mEnrollmentIds = new HashSet<>();
@@ -224,6 +230,25 @@
     public void cleanupInternalState(int userId)  {
         Utils.checkPermission(mContext, TEST_BIOMETRIC);
 
-        mProvider.scheduleInternalCleanup(mSensorId, userId);
+        mProvider.scheduleInternalCleanup(mSensorId, userId, new BaseClientMonitor.Callback() {
+            @Override
+            public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
+                try {
+                    mCallback.onCleanupStarted(clientMonitor.getTargetUserId());
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Remote exception", e);
+                }
+            }
+
+            @Override
+            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+                    boolean success) {
+                try {
+                    mCallback.onCleanupFinished(clientMonitor.getTargetUserId());
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Remote exception", e);
+                }
+            }
+        });
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
index 9680e4e..c6696aed 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
@@ -61,7 +61,7 @@
         // Internal remove does not need to send results to anyone. Cleanup (enumerate + remove)
         // is all done internally.
         return new FaceRemovalClient(context, lazyDaemon, token,
-                null /* ClientMonitorCallbackConverter */, biometricId, userId, owner, utils,
-                sensorId, authenticatorIds);
+                null /* ClientMonitorCallbackConverter */, new int[] {biometricId}, userId, owner,
+                utils, sensorId, authenticatorIds);
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index 1b6b9d7..1d8f210 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -25,6 +25,7 @@
 import android.content.pm.UserInfo;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.face.IFace;
 import android.hardware.biometrics.face.SensorProps;
 import android.hardware.face.Face;
@@ -177,7 +178,8 @@
         for (int i = 0; i < mSensors.size(); i++) {
             final int sensorId = mSensors.keyAt(i);
             scheduleLoadAuthenticatorIds(sensorId);
-            scheduleInternalCleanup(sensorId, ActivityManager.getCurrentUser());
+            scheduleInternalCleanup(sensorId, ActivityManager.getCurrentUser(),
+                    null /* callback */);
         }
 
         return mDaemon;
@@ -468,6 +470,25 @@
     @Override
     public void scheduleRemove(int sensorId, @NonNull IBinder token, int faceId, int userId,
             @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
+        scheduleRemoveSpecifiedIds(sensorId, token, new int[] {faceId}, userId, receiver,
+                opPackageName);
+    }
+
+    @Override
+    public void scheduleRemoveAll(int sensorId, @NonNull IBinder token, int userId,
+            @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
+        final List<Face> faces = FaceUtils.getInstance(sensorId)
+                .getBiometricsForUser(mContext, userId);
+        final int[] faceIds = new int[faces.size()];
+        for (int i = 0; i < faces.size(); i++) {
+            faceIds[i] = faces.get(i).getBiometricId();
+        }
+
+        scheduleRemoveSpecifiedIds(sensorId, token, faceIds, userId, receiver, opPackageName);
+    }
+
+    private void scheduleRemoveSpecifiedIds(int sensorId, @NonNull IBinder token, int[] faceIds,
+            int userId, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
         mHandler.post(() -> {
             final IFace daemon = getHalInstance();
             if (daemon == null) {
@@ -485,7 +506,7 @@
 
                 final FaceRemovalClient client = new FaceRemovalClient(mContext,
                         mSensors.get(sensorId).getLazySession(), token,
-                        new ClientMonitorCallbackConverter(receiver), faceId, userId,
+                        new ClientMonitorCallbackConverter(receiver), faceIds, userId,
                         opPackageName, FaceUtils.getInstance(sensorId), sensorId,
                         mSensors.get(sensorId).getAuthenticatorIds());
 
@@ -543,7 +564,8 @@
     }
 
     @Override
-    public void scheduleInternalCleanup(int sensorId, int userId) {
+    public void scheduleInternalCleanup(int sensorId, int userId,
+            @Nullable BaseClientMonitor.Callback callback) {
         mHandler.post(() -> {
             final IFace daemon = getHalInstance();
             if (daemon == null) {
@@ -564,7 +586,7 @@
                                 FaceUtils.getInstance(sensorId),
                                 mSensors.get(sensorId).getAuthenticatorIds());
 
-                mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+                mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client, callback);
             } catch (RemoteException e) {
                 Slog.e(getTag(), "Remote exception when scheduling internal cleanup", e);
             }
@@ -627,8 +649,9 @@
 
     @NonNull
     @Override
-    public ITestSession createTestSession(int sensorId, @NonNull String opPackageName) {
-        return mSensors.get(sensorId).createTestSession();
+    public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
+            @NonNull String opPackageName) {
+        return mSensors.get(sensorId).createTestSession(callback);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java
index 1cb5031..48796c1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java
@@ -38,19 +38,22 @@
 class FaceRemovalClient extends RemovalClient<Face, ISession> {
     private static final String TAG = "FaceRemovalClient";
 
+    final int[] mBiometricIds;
+
     FaceRemovalClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
-            int biometricId, int userId, @NonNull String owner, @NonNull BiometricUtils<Face> utils,
-            int sensorId, @NonNull Map<Integer, Long> authenticatorIds) {
-        super(context, lazyDaemon, token, listener, biometricId, userId, owner, utils, sensorId,
+            int[] biometricIds, int userId, @NonNull String owner,
+            @NonNull BiometricUtils<Face> utils, int sensorId,
+            @NonNull Map<Integer, Long> authenticatorIds) {
+        super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId,
                 authenticatorIds, BiometricsProtoEnums.MODALITY_FACE);
+        mBiometricIds = biometricIds;
     }
 
     @Override
     protected void startHalOperation() {
         try {
-            final int[] ids = new int[]{mBiometricId};
-            getFreshDaemon().removeEnrollments(mSequentialId, ids);
+            getFreshDaemon().removeEnrollments(mSequentialId, mBiometricIds);
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting remove", e);
             mCallback.onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index 4925ce0..3434acb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -22,6 +22,7 @@
 import android.content.pm.UserInfo;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.face.AuthenticationFrame;
 import android.hardware.biometrics.face.EnrollmentFrame;
 import android.hardware.biometrics.face.Error;
@@ -459,8 +460,9 @@
         }
     }
 
-    @NonNull ITestSession createTestSession() {
-        return new BiometricTestSessionImpl(mContext, mSensorProperties.sensorId, mProvider, this);
+    @NonNull ITestSession createTestSession(@NonNull ITestSessionCallback callback) {
+        return new BiometricTestSessionImpl(mContext, mSensorProperties.sensorId, callback,
+                mProvider, this);
     }
 
     void createNewSession(@NonNull IFace daemon, int sensorId, int userId)
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
index d519d60..e8668ed 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
@@ -21,14 +21,17 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.face.Face;
 import android.hardware.face.FaceAuthenticationFrame;
 import android.hardware.face.FaceEnrollFrame;
 import android.hardware.face.IFaceServiceReceiver;
 import android.os.Binder;
+import android.os.RemoteException;
 import android.util.Slog;
 
 import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.face.FaceUtils;
 
 import java.util.ArrayList;
@@ -43,6 +46,7 @@
 
     @NonNull private final Context mContext;
     private final int mSensorId;
+    @NonNull private final ITestSessionCallback mCallback;
     @NonNull private final Face10 mFace10;
     @NonNull private final Face10.HalResultController mHalResultController;
     @NonNull private final Set<Integer> mEnrollmentIds;
@@ -120,10 +124,12 @@
         }
     };
 
-    BiometricTestSessionImpl(@NonNull Context context, int sensorId, @NonNull Face10 face10,
+    BiometricTestSessionImpl(@NonNull Context context, int sensorId,
+            @NonNull ITestSessionCallback callback, @NonNull Face10 face10,
             @NonNull Face10.HalResultController halResultController) {
         mContext = context;
         mSensorId = sensorId;
+        mCallback = callback;
         mFace10 = face10;
         mHalResultController = halResultController;
         mEnrollmentIds = new HashSet<>();
@@ -201,6 +207,25 @@
     public void cleanupInternalState(int userId) {
         Utils.checkPermission(mContext, TEST_BIOMETRIC);
 
-        mFace10.scheduleInternalCleanup(mSensorId, userId);
+        mFace10.scheduleInternalCleanup(mSensorId, userId, new BaseClientMonitor.Callback() {
+            @Override
+            public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
+                try {
+                    mCallback.onCleanupStarted(clientMonitor.getTargetUserId());
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Remote exception", e);
+                }
+            }
+
+            @Override
+            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+                    boolean success) {
+                try {
+                    mCallback.onCleanupFinished(clientMonitor.getTargetUserId());
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Remote exception", e);
+                }
+            }
+        });
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index e46661a..ee8823e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -29,6 +29,7 @@
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.face.V1_0.IBiometricsFace;
 import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback;
 import android.hardware.face.Face;
@@ -123,7 +124,7 @@
     private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() {
         @Override
         public void onUserSwitching(int newUserId) {
-            scheduleInternalCleanup(newUserId);
+            scheduleInternalCleanup(newUserId, null /* callback */);
             scheduleGetFeature(mSensorId, new Binder(), newUserId,
                     BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION,
                     null, mContext.getOpPackageName());
@@ -437,7 +438,7 @@
         Slog.d(TAG, "Face HAL ready, HAL ID: " + halId);
         if (halId != 0) {
             scheduleLoadAuthenticatorIds();
-            scheduleInternalCleanup(ActivityManager.getCurrentUser());
+            scheduleInternalCleanup(ActivityManager.getCurrentUser(), null /* callback */);
             scheduleGetFeature(mSensorId, new Binder(),
                     ActivityManager.getCurrentUser(),
                     BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION, null,
@@ -671,6 +672,20 @@
         });
     }
 
+    @Override
+    public void scheduleRemoveAll(int sensorId, @NonNull IBinder token, int userId,
+            @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
+        mHandler.post(() -> {
+            scheduleUpdateActiveUserWithoutHandler(userId);
+
+            // For IBiometricsFace@1.0, remove(0) means remove all enrollments
+            final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token,
+                    new ClientMonitorCallbackConverter(receiver), 0 /* faceId */, userId,
+                    opPackageName,
+                    FaceUtils.getLegacyInstance(mSensorId), mSensorId, mAuthenticatorIds);
+            mScheduler.scheduleClientMonitor(client);
+        });
+    }
 
     @Override
     public void scheduleResetLockout(int sensorId, int userId, @NonNull byte[] hardwareAuthToken) {
@@ -742,7 +757,8 @@
         });
     }
 
-    private void scheduleInternalCleanup(int userId) {
+    private void scheduleInternalCleanup(int userId,
+            @Nullable BaseClientMonitor.Callback callback) {
         mHandler.post(() -> {
             scheduleUpdateActiveUserWithoutHandler(userId);
 
@@ -750,13 +766,14 @@
             final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext,
                     mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId, enrolledList,
                     FaceUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
-            mScheduler.scheduleClientMonitor(client);
+            mScheduler.scheduleClientMonitor(client, callback);
         });
     }
 
     @Override
-    public void scheduleInternalCleanup(int sensorId, int userId) {
-        scheduleInternalCleanup(userId);
+    public void scheduleInternalCleanup(int sensorId, int userId,
+            @Nullable BaseClientMonitor.Callback callback) {
+        scheduleInternalCleanup(userId, callback);
     }
 
     @Override
@@ -930,7 +947,9 @@
 
     @NonNull
     @Override
-    public ITestSession createTestSession(int sensorId, @NonNull String opPackageName) {
-        return new BiometricTestSessionImpl(mContext, mSensorId, this, mHalResultController);
+    public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
+            @NonNull String opPackageName) {
+        return new BiometricTestSessionImpl(mContext, mSensorId, callback, this,
+                mHalResultController);
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java
index d63791c..3ae2011 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java
@@ -38,12 +38,15 @@
 class FaceRemovalClient extends RemovalClient<Face, IBiometricsFace> {
     private static final String TAG = "FaceRemovalClient";
 
+    private final int mBiometricId;
+
     FaceRemovalClient(@NonNull Context context, @NonNull LazyDaemon<IBiometricsFace> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
             int biometricId, int userId, @NonNull String owner, @NonNull BiometricUtils<Face> utils,
             int sensorId, @NonNull Map<Integer, Long> authenticatorIds) {
-        super(context, lazyDaemon, token, listener, biometricId, userId, owner, utils, sensorId,
+        super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId,
                 authenticatorIds, BiometricsProtoEnums.MODALITY_FACE);
+        mBiometricId = biometricId;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
index 34a9099..32e9409 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
@@ -21,6 +21,7 @@
 import android.hardware.biometrics.IBiometricSensorReceiver;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.SensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintService;
 import android.os.IBinder;
@@ -42,8 +43,9 @@
     }
 
     @Override
-    public ITestSession createTestSession(@NonNull String opPackageName) throws RemoteException {
-        return mFingerprintService.createTestSession(mSensorId, opPackageName);
+    public ITestSession createTestSession(@NonNull ITestSessionCallback callback,
+            @NonNull String opPackageName) throws RemoteException {
+        return mFingerprintService.createTestSession(mSensorId, callback, opPackageName);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index b0e42cd..396dd5f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -43,6 +43,7 @@
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.fingerprint.IFingerprint;
 import android.hardware.biometrics.fingerprint.SensorProps;
 import android.hardware.fingerprint.Fingerprint;
@@ -109,7 +110,8 @@
      */
     private final class FingerprintServiceWrapper extends IFingerprintService.Stub {
         @Override
-        public ITestSession createTestSession(int sensorId, @NonNull String opPackageName) {
+        public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
+                @NonNull String opPackageName) {
             Utils.checkPermission(getContext(), TEST_BIOMETRIC);
 
             final ServiceProvider provider = getProviderForSensor(sensorId);
@@ -119,7 +121,7 @@
                 return null;
             }
 
-            return provider.createTestSession(sensorId, opPackageName);
+            return provider.createTestSession(sensorId, callback, opPackageName);
         }
 
         @Override
@@ -499,7 +501,21 @@
                     opPackageName);
         }
 
-        @Override
+        @Override // Binder call
+        public void removeAll(final IBinder token, final int userId,
+                final IFingerprintServiceReceiver receiver, final String opPackageName) {
+            Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
+
+            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+            if (provider == null) {
+                Slog.w(TAG, "Null provider for removeAll");
+                return;
+            }
+            provider.second.scheduleRemoveAll(provider.first, token, receiver, userId,
+                    opPackageName);
+        }
+
+        @Override // Binder call
         public void addLockoutResetCallback(final IBiometricServiceLockoutResetCallback callback,
                 final String opPackageName) {
             Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index f672ae5..dfec2e3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -28,6 +29,7 @@
 import android.os.IBinder;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.LockoutTracker;
 
@@ -98,7 +100,12 @@
             @NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId,
             @NonNull String opPackageName);
 
-    void scheduleInternalCleanup(int sensorId, int userId);
+    void scheduleRemoveAll(int sensorId, @NonNull IBinder token,
+            @NonNull IFingerprintServiceReceiver receiver, int userId,
+            @NonNull String opPackageName);
+
+    void scheduleInternalCleanup(int sensorId, int userId,
+            @Nullable BaseClientMonitor.Callback callback);
 
     boolean isHardwareDetected(int sensorId);
 
@@ -133,5 +140,6 @@
     void dumpInternal(int sensorId, @NonNull PrintWriter pw);
 
     @NonNull
-    ITestSession createTestSession(int sensorId, @NonNull String opPackageName);
+    ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
+            @NonNull String opPackageName);
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
index ea9c709..20b3254 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
@@ -21,14 +21,17 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
 import android.os.Binder;
+import android.os.RemoteException;
 import android.util.Slog;
 
 import com.android.server.biometrics.HardwareAuthTokenUtils;
 import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
 
 import java.util.HashSet;
@@ -46,6 +49,7 @@
 
     @NonNull private final Context mContext;
     private final int mSensorId;
+    @NonNull private final ITestSessionCallback mCallback;
     @NonNull private final FingerprintProvider mProvider;
     @NonNull private final Sensor mSensor;
     @NonNull private final Set<Integer> mEnrollmentIds;
@@ -110,9 +114,11 @@
     };
 
     BiometricTestSessionImpl(@NonNull Context context, int sensorId,
-            @NonNull FingerprintProvider provider, @NonNull Sensor sensor) {
+            @NonNull ITestSessionCallback callback, @NonNull FingerprintProvider provider,
+            @NonNull Sensor sensor) {
         mContext = context;
         mSensorId = sensorId;
+        mCallback = callback;
         mProvider = provider;
         mSensor = sensor;
         mEnrollmentIds = new HashSet<>();
@@ -192,6 +198,25 @@
     public void cleanupInternalState(int userId)  {
         Utils.checkPermission(mContext, TEST_BIOMETRIC);
 
-        mProvider.scheduleInternalCleanup(mSensorId, userId);
+        mProvider.scheduleInternalCleanup(mSensorId, userId, new BaseClientMonitor.Callback() {
+            @Override
+            public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
+                try {
+                    mCallback.onCleanupStarted(clientMonitor.getTargetUserId());
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Remote exception", e);
+                }
+            }
+
+            @Override
+            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+                    boolean success) {
+                try {
+                    mCallback.onCleanupFinished(clientMonitor.getTargetUserId());
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Remote exception", e);
+                }
+            }
+        });
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
index 2a0e984..0de3f4f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
@@ -60,7 +60,7 @@
             String owner, BiometricUtils<Fingerprint> utils, int sensorId,
             Map<Integer, Long> authenticatorIds) {
         return new FingerprintRemovalClient(context, lazyDaemon, token,
-                null /* ClientMonitorCallbackConverter */, biometricId, userId, owner, utils,
-                sensorId, authenticatorIds);
+                null /* ClientMonitorCallbackConverter */, new int[] {biometricId}, userId, owner,
+                utils, sensorId, authenticatorIds);
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 0bd2f24..598cc89 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -25,6 +25,7 @@
 import android.content.pm.UserInfo;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.fingerprint.IFingerprint;
 import android.hardware.biometrics.fingerprint.SensorProps;
 import android.hardware.fingerprint.Fingerprint;
@@ -185,7 +186,8 @@
         for (int i = 0; i < mSensors.size(); i++) {
             final int sensorId = mSensors.keyAt(i);
             scheduleLoadAuthenticatorIds(sensorId);
-            scheduleInternalCleanup(sensorId, ActivityManager.getCurrentUser());
+            scheduleInternalCleanup(sensorId, ActivityManager.getCurrentUser(),
+                    null /* callback */);
         }
 
         return mDaemon;
@@ -490,6 +492,27 @@
     public void scheduleRemove(int sensorId, @NonNull IBinder token,
             @NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId,
             @NonNull String opPackageName) {
+        scheduleRemoveSpecifiedIds(sensorId, token, new int[] {fingerId}, userId, receiver,
+                opPackageName);
+    }
+
+    @Override
+    public void scheduleRemoveAll(int sensorId, @NonNull IBinder token,
+            @NonNull IFingerprintServiceReceiver receiver, int userId,
+            @NonNull String opPackageName) {
+        final List<Fingerprint> fingers = FingerprintUtils.getInstance(sensorId)
+                .getBiometricsForUser(mContext, userId);
+        final int[] fingerIds = new int[fingers.size()];
+        for (int i = 0; i < fingers.size(); i++) {
+            fingerIds[i] = fingers.get(i).getBiometricId();
+        }
+
+        scheduleRemoveSpecifiedIds(sensorId, token, fingerIds, userId, receiver, opPackageName);
+    }
+
+    private void scheduleRemoveSpecifiedIds(int sensorId, @NonNull IBinder token,
+            int[] fingerprintIds, int userId, @NonNull IFingerprintServiceReceiver receiver,
+            @NonNull String opPackageName) {
         mHandler.post(() -> {
             final IFingerprint daemon = getHalInstance();
             if (daemon == null) {
@@ -507,7 +530,7 @@
 
                 final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext,
                         mSensors.get(sensorId).getLazySession(), token,
-                        new ClientMonitorCallbackConverter(receiver), fingerId, userId,
+                        new ClientMonitorCallbackConverter(receiver), fingerprintIds, userId,
                         opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
                         mSensors.get(sensorId).getAuthenticatorIds());
                 mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
@@ -518,7 +541,8 @@
     }
 
     @Override
-    public void scheduleInternalCleanup(int sensorId, int userId) {
+    public void scheduleInternalCleanup(int sensorId, int userId,
+            @Nullable BaseClientMonitor.Callback callback) {
         mHandler.post(() -> {
             final IFingerprint daemon = getHalInstance();
             if (daemon == null) {
@@ -538,7 +562,7 @@
                                 mContext.getOpPackageName(), sensorId, enrolledList,
                                 FingerprintUtils.getInstance(sensorId),
                                 mSensors.get(sensorId).getAuthenticatorIds());
-                mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+                mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client, callback);
             } catch (RemoteException e) {
                 Slog.e(getTag(), "Remote exception when scheduling internal cleanup", e);
             }
@@ -683,8 +707,9 @@
 
     @NonNull
     @Override
-    public ITestSession createTestSession(int sensorId, @NonNull String opPackageName) {
-        return mSensors.get(sensorId).createTestSession();
+    public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
+            @NonNull String opPackageName) {
+        return mSensors.get(sensorId).createTestSession(callback);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java
index 4a99a7b..c622208 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java
@@ -39,20 +39,22 @@
 class FingerprintRemovalClient extends RemovalClient<Fingerprint, ISession> {
     private static final String TAG = "FingerprintRemovalClient";
 
+    private final int[] mBiometricIds;
+
     FingerprintRemovalClient(@NonNull Context context,
             @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token,
-            @Nullable ClientMonitorCallbackConverter listener, int biometricId, int userId,
+            @Nullable ClientMonitorCallbackConverter listener, int[] biometricIds, int userId,
             @NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
             @NonNull Map<Integer, Long> authenticatorIds) {
-        super(context, lazyDaemon, token, listener, biometricId, userId, owner, utils, sensorId,
+        super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId,
                 authenticatorIds, BiometricsProtoEnums.MODALITY_FINGERPRINT);
+        mBiometricIds = biometricIds;
     }
 
     @Override
     protected void startHalOperation() {
         try {
-            final int[] ids = new int[] {mBiometricId};
-            getFreshDaemon().removeEnrollments(mSequentialId, ids);
+            getFreshDaemon().removeEnrollments(mSequentialId, mBiometricIds);
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting remove", e);
             mCallback.onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index c83c0fb..a98e7db 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -22,6 +22,7 @@
 import android.content.pm.UserInfo;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.fingerprint.Error;
 import android.hardware.biometrics.fingerprint.IFingerprint;
 import android.hardware.biometrics.fingerprint.ISession;
@@ -439,8 +440,9 @@
         }
     }
 
-    @NonNull ITestSession createTestSession() {
-        return new BiometricTestSessionImpl(mContext, mSensorProperties.sensorId, mProvider, this);
+    @NonNull ITestSession createTestSession(@NonNull ITestSessionCallback callback) {
+        return new BiometricTestSessionImpl(mContext, mSensorProperties.sensorId, callback,
+                mProvider, this);
     }
 
     void createNewSession(@NonNull IFingerprint daemon, int sensorId, int userId)
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
index 312ee0a..766a882 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
@@ -21,13 +21,16 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
 import android.os.Binder;
+import android.os.RemoteException;
 import android.util.Slog;
 
 import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
 
 import java.util.ArrayList;
@@ -47,6 +50,7 @@
 
     @NonNull private final Context mContext;
     private final int mSensorId;
+    @NonNull private final ITestSessionCallback mCallback;
     @NonNull private final Fingerprint21 mFingerprint21;
     @NonNull private final Fingerprint21.HalResultController mHalResultController;
     @NonNull private final Set<Integer> mEnrollmentIds;
@@ -111,10 +115,12 @@
     };
 
     BiometricTestSessionImpl(@NonNull Context context, int sensorId,
+            @NonNull ITestSessionCallback callback,
             @NonNull Fingerprint21 fingerprint21,
             @NonNull Fingerprint21.HalResultController halResultController) {
         mContext = context;
         mSensorId = sensorId;
+        mCallback = callback;
         mFingerprint21 = fingerprint21;
         mHalResultController = halResultController;
         mEnrollmentIds = new HashSet<>();
@@ -191,6 +197,25 @@
     public void cleanupInternalState(int userId)  {
         Utils.checkPermission(mContext, TEST_BIOMETRIC);
 
-        mFingerprint21.scheduleInternalCleanup(mSensorId, userId);
+        mFingerprint21.scheduleInternalCleanup(mSensorId, userId, new BaseClientMonitor.Callback() {
+            @Override
+            public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
+                try {
+                    mCallback.onCleanupStarted(clientMonitor.getTargetUserId());
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Remote exception", e);
+                }
+            }
+
+            @Override
+            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+                    boolean success) {
+                try {
+                    mCallback.onCleanupFinished(clientMonitor.getTargetUserId());
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Remote exception", e);
+                }
+            }
+        });
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index 7a74c6a..6e22a79 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -30,6 +30,7 @@
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprintClientCallback;
 import android.hardware.fingerprint.Fingerprint;
@@ -158,7 +159,7 @@
     private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() {
         @Override
         public void onUserSwitching(int newUserId) {
-            scheduleInternalCleanup(newUserId);
+            scheduleInternalCleanup(newUserId, null /* callback */);
         }
     };
 
@@ -437,7 +438,7 @@
         Slog.d(TAG, "Fingerprint HAL ready, HAL ID: " + halId);
         if (halId != 0) {
             scheduleLoadAuthenticatorIds();
-            scheduleInternalCleanup(ActivityManager.getCurrentUser());
+            scheduleInternalCleanup(ActivityManager.getCurrentUser(), null /* callback */);
         } else {
             Slog.e(TAG, "Unable to set callback");
             mDaemon = null;
@@ -463,26 +464,33 @@
             for (UserInfo user : UserManager.get(mContext).getAliveUsers()) {
                 final int targetUserId = user.id;
                 if (!mAuthenticatorIds.containsKey(targetUserId)) {
-                    scheduleUpdateActiveUserWithoutHandler(targetUserId);
+                    scheduleUpdateActiveUserWithoutHandler(targetUserId, true /* force */);
                 }
             }
         });
     }
 
+    private void scheduleUpdateActiveUserWithoutHandler(int targetUserId) {
+        scheduleUpdateActiveUserWithoutHandler(targetUserId, false /* force */);
+    }
+
     /**
      * Schedules the {@link FingerprintUpdateActiveUserClient} without posting the work onto the
      * handler. Many/most APIs are user-specific. However, the HAL requires explicit "setActiveUser"
      * invocation prior to authenticate/enroll/etc. Thus, internally we usually want to schedule
      * this operation on the same lambda/runnable as those operations so that the ordering is
      * correct.
+     *
+     * @param targetUserId Switch to this user, and update their authenticatorId
+     * @param force Always retrieve the authenticatorId, even if we are already the targetUserId
      */
-    private void scheduleUpdateActiveUserWithoutHandler(int targetUserId) {
+    private void scheduleUpdateActiveUserWithoutHandler(int targetUserId, boolean force) {
         final boolean hasEnrolled =
                 !getEnrolledFingerprints(mSensorProperties.sensorId, targetUserId).isEmpty();
         final FingerprintUpdateActiveUserClient client =
                 new FingerprintUpdateActiveUserClient(mContext, mLazyDaemon, targetUserId,
                         mContext.getOpPackageName(), mSensorProperties.sensorId, mCurrentUserId,
-                        hasEnrolled, mAuthenticatorIds);
+                        hasEnrolled, mAuthenticatorIds, force);
         mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() {
             @Override
             public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
@@ -563,7 +571,8 @@
                         boolean success) {
                     if (success) {
                         // Update authenticatorIds
-                        scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId());
+                        scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId(),
+                                true /* force */);
                     }
                 }
             });
@@ -636,7 +645,25 @@
         });
     }
 
-    private void scheduleInternalCleanup(int userId) {
+    @Override
+    public void scheduleRemoveAll(int sensorId, @NonNull IBinder token,
+            @NonNull IFingerprintServiceReceiver receiver, int userId,
+            @NonNull String opPackageName) {
+        mHandler.post(() -> {
+            scheduleUpdateActiveUserWithoutHandler(userId);
+
+            // For IBiometricsFingerprint@2.1, remove(0) means remove all enrollments
+            final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext,
+                    mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver),
+                    0 /* fingerprintId */, userId, opPackageName,
+                    FingerprintUtils.getLegacyInstance(mSensorId),
+                    mSensorProperties.sensorId, mAuthenticatorIds);
+            mScheduler.scheduleClientMonitor(client);
+        });
+    }
+
+    private void scheduleInternalCleanup(int userId,
+            @Nullable BaseClientMonitor.Callback callback) {
         mHandler.post(() -> {
             scheduleUpdateActiveUserWithoutHandler(userId);
 
@@ -646,13 +673,14 @@
                     mContext, mLazyDaemon, userId, mContext.getOpPackageName(),
                     mSensorProperties.sensorId, enrolledList,
                     FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
-            mScheduler.scheduleClientMonitor(client);
+            mScheduler.scheduleClientMonitor(client, callback);
         });
     }
 
     @Override
-    public void scheduleInternalCleanup(int sensorId, int userId) {
-        scheduleInternalCleanup(userId);
+    public void scheduleInternalCleanup(int sensorId, int userId,
+            @Nullable BaseClientMonitor.Callback callback) {
+        scheduleInternalCleanup(userId, callback);
     }
 
     @Override
@@ -832,8 +860,9 @@
 
     @NonNull
     @Override
-    public ITestSession createTestSession(int sensorId, @NonNull String opPackageName) {
-        return new BiometricTestSessionImpl(mContext, mSensorProperties.sensorId, this,
+    public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
+            @NonNull String opPackageName) {
+        return new BiometricTestSessionImpl(mContext, mSensorProperties.sensorId, callback, this,
                 mHalResultController);
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java
index f6a22f5..2f360f3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java
@@ -39,13 +39,16 @@
 class FingerprintRemovalClient extends RemovalClient<Fingerprint, IBiometricsFingerprint> {
     private static final String TAG = "FingerprintRemovalClient";
 
+    private final int mBiometricId;
+
     FingerprintRemovalClient(@NonNull Context context,
             @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
             @NonNull ClientMonitorCallbackConverter listener, int biometricId, int userId,
             @NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
             @NonNull Map<Integer, Long> authenticatorIds) {
-        super(context, lazyDaemon, token, listener, biometricId, userId, owner, utils, sensorId,
+        super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId,
                 authenticatorIds, BiometricsProtoEnums.MODALITY_FINGERPRINT);
+        mBiometricId = biometricId;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
index db7f4bc..6776810 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
@@ -41,6 +41,7 @@
     private static final String FP_DATA_DIR = "fpdata";
 
     private final int mCurrentUserId;
+    private final boolean mForceUpdateAuthenticatorId;
     private final boolean mHasEnrolledBiometrics;
     private final Map<Integer, Long> mAuthenticatorIds;
     private File mDirectory;
@@ -48,11 +49,12 @@
     FingerprintUpdateActiveUserClient(@NonNull Context context,
             @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, int userId,
             @NonNull String owner, int sensorId, int currentUserId, boolean hasEnrolledBiometrics,
-            @NonNull Map<Integer, Long> authenticatorIds) {
+            @NonNull Map<Integer, Long> authenticatorIds, boolean forceUpdateAuthenticatorId) {
         super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
                 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
                 BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
         mCurrentUserId = currentUserId;
+        mForceUpdateAuthenticatorId = forceUpdateAuthenticatorId;
         mHasEnrolledBiometrics = hasEnrolledBiometrics;
         mAuthenticatorIds = authenticatorIds;
     }
@@ -61,7 +63,7 @@
     public void start(@NonNull Callback callback) {
         super.start(callback);
 
-        if (mCurrentUserId == getTargetUserId()) {
+        if (mCurrentUserId == getTargetUserId() && !mForceUpdateAuthenticatorId) {
             Slog.d(TAG, "Already user: " + mCurrentUserId + ", returning");
             callback.onClientFinished(this, true /* success */);
             return;
diff --git a/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
index 8e84613..f44e069 100644
--- a/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
@@ -21,6 +21,7 @@
 import android.hardware.biometrics.IBiometricSensorReceiver;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.SensorPropertiesInternal;
 import android.hardware.iris.IIrisService;
 import android.os.IBinder;
@@ -39,7 +40,8 @@
     }
 
     @Override
-    public ITestSession createTestSession(@NonNull String opPackageName) throws RemoteException {
+    public ITestSession createTestSession(@NonNull ITestSessionCallback callback,
+            @NonNull String opPackageName) throws RemoteException {
         return null;
     }
 
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
index 09c0937..01e839d 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
@@ -41,7 +41,6 @@
 import com.android.internal.util.Preconditions;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
-import com.android.server.security.FileIntegrityService;
 import com.android.server.security.VerityUtils;
 
 import java.io.File;
@@ -226,7 +225,7 @@
     @Nullable
     private static UpdatableFontDir createUpdatableFontDir() {
         // If apk verity is supported, fs-verity should be available.
-        if (!FileIntegrityService.isApkVeritySupported()) return null;
+        if (!VerityUtils.isFsVeritySupported()) return null;
         return new UpdatableFontDir(new File(FONT_FILES_DIR),
                 Arrays.asList(new File(SystemFonts.SYSTEM_FONT_DIR),
                         new File(SystemFonts.OEM_FONT_DIR)),
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index fa1fb48..d014f14 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -774,8 +774,14 @@
 
     private void initializeCec(int initiatedBy) {
         mAddressAllocated = false;
-        mCecVersion = getHdmiCecConfig().getIntValue(
+        int settingsCecVersion = getHdmiCecConfig().getIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION);
+        int supportedCecVersion = mCecController.getVersion();
+
+        // Limit the used CEC version to the highest supported version by HAL and selected
+        // version in settings (but at least v1.4b).
+        mCecVersion = Math.max(HdmiControlManager.HDMI_CEC_VERSION_1_4_B,
+                Math.min(settingsCecVersion, supportedCecVersion));
 
         mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, true);
         mCecController.setLanguage(mMenuLanguage);
@@ -2184,6 +2190,7 @@
 
             pw.println("mProhibitMode: " + mProhibitMode);
             pw.println("mPowerStatus: " + mPowerStatusController.getPowerStatus());
+            pw.println("mCecVersion: " + mCecVersion);
 
             // System settings
             pw.println("System_settings:");
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlShellCommand.java b/services/core/java/com/android/server/hdmi/HdmiControlShellCommand.java
index ee3427f..a1e6136 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlShellCommand.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlShellCommand.java
@@ -70,6 +70,10 @@
         pw.println("                --args <vendor specific arguments>");
         pw.println("                [--id <true if vendor command should be sent with vendor id>]");
         pw.println("      Send a Vendor Command to the given target device");
+        pw.println("  cec_setting get <setting name>");
+        pw.println("      Get the current value of a CEC setting");
+        pw.println("  cec_setting set <setting name> <value>");
+        pw.println("      Set the value of a CEC setting");
     }
 
     private int handleShellCommand(String cmd) throws RemoteException {
@@ -81,6 +85,8 @@
                 return oneTouchPlay(pw);
             case "vendorcommand":
                 return vendorCommand(pw);
+            case "cec_setting":
+                return cecSetting(pw);
         }
 
         getErrPrintWriter().println("Unhandled command: " + cmd);
@@ -157,4 +163,39 @@
         mBinderService.sendVendorCommand(deviceType, destination, params, hasVendorId);
         return 0;
     }
+
+    private int cecSetting(PrintWriter pw) throws RemoteException {
+        if (getRemainingArgsCount() < 1) {
+            throw new IllegalArgumentException("Expected at least 1 argument (operation).");
+        }
+        String operation = getNextArgRequired();
+        switch (operation) {
+            case "get": {
+                String setting = getNextArgRequired();
+                try {
+                    String value = mBinderService.getCecSettingStringValue(setting);
+                    pw.println(setting + " = " + value);
+                } catch (IllegalArgumentException e) {
+                    int intValue = mBinderService.getCecSettingIntValue(setting);
+                    pw.println(setting + " = " + intValue);
+                }
+                return 0;
+            }
+            case "set": {
+                String setting = getNextArgRequired();
+                String value = getNextArgRequired();
+                try {
+                    mBinderService.setCecSettingStringValue(setting, value);
+                    pw.println(setting + " = " + value);
+                } catch (IllegalArgumentException e) {
+                    int intValue = Integer.parseInt(value);
+                    mBinderService.setCecSettingIntValue(setting, intValue);
+                    pw.println(setting + " = " + intValue);
+                }
+                return 0;
+            }
+            default:
+                throw new IllegalArgumentException("Unknown operation: " + operation);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
index bd577f3..c4c0f68 100644
--- a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
+++ b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
@@ -371,9 +371,6 @@
             int durationMs) {
         Slog.i(mTag, "setTemporaryService(" + userId + ") to " + componentName + " for "
                 + durationMs + "ms");
-        if (mServiceNameResolver == null) {
-            return;
-        }
         enforceCallingPermissionForManagement();
 
         Objects.requireNonNull(componentName);
@@ -407,9 +404,6 @@
         enforceCallingPermissionForManagement();
 
         synchronized (mLock) {
-            if (mServiceNameResolver == null) {
-                return false;
-            }
             final boolean changed = mServiceNameResolver.setDefaultServiceEnabled(userId, enabled);
             if (!changed) {
                 if (verbose) {
@@ -440,10 +434,6 @@
     public final boolean isDefaultServiceEnabled(@UserIdInt int userId) {
         enforceCallingPermissionForManagement();
 
-        if (mServiceNameResolver == null) {
-            return false;
-        }
-
         synchronized (mLock) {
             return mServiceNameResolver.isDefaultServiceEnabled(userId);
         }
@@ -968,10 +958,6 @@
             public void onPackageModified(String packageName) {
                 if (verbose) Slog.v(mTag, "onPackageModified(): " + packageName);
 
-                if (mServiceNameResolver == null) {
-                    return;
-                }
-
                 final int userId = getChangingUserId();
                 final String serviceName = mServiceNameResolver.getDefaultServiceName(userId);
                 if (serviceName == null) {
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 5eec315..6deb19a 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -1424,6 +1424,8 @@
             mAppForegroundHelper.onSystemReady();
             mLocationPowerSaveModeHelper.onSystemReady();
             mScreenInteractiveHelper.onSystemReady();
+            mDeviceStationaryHelper.onSystemReady();
+            mDeviceIdleHelper.onSystemReady();
 
             if (mEmergencyCallHelper != null) {
                 mEmergencyCallHelper.onSystemReady();
diff --git a/services/core/java/com/android/server/location/contexthub/AuthStateDenialTimer.java b/services/core/java/com/android/server/location/contexthub/AuthStateDenialTimer.java
new file mode 100644
index 0000000..85de4bb
--- /dev/null
+++ b/services/core/java/com/android/server/location/contexthub/AuthStateDenialTimer.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.location.contexthub;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+
+/**
+ * A class that manages a timer used to keep track of how much time is left before a
+ * {@link ContextHubClientBroker} has its authorization state changed with a nanoapp from
+ * DENIED_GRACE_PERIOD to DENIED. Much of this implementation is copied from
+ * {@link android.os.CountDownTimer} while adding the ability to specify the provided looper.
+ *
+ * @hide
+ */
+public class AuthStateDenialTimer {
+    private static final long TIMEOUT_MS = SECONDS.toMillis(60);
+
+    private final ContextHubClientBroker mClient;
+    private final long mNanoAppId;
+    private final Handler mHandler;
+
+    /**
+     * Indicates when the timer should stop in the future.
+     */
+    private long mStopTimeInFuture;
+
+    /**
+     * boolean representing if the timer was cancelled
+     */
+    private boolean mCancelled = false;
+
+    public AuthStateDenialTimer(ContextHubClientBroker client, long nanoAppId, Looper looper) {
+        mClient = client;
+        mNanoAppId = nanoAppId;
+        mHandler = new CountDownHandler(looper);
+    }
+
+    /**
+     * Cancel the countdown.
+     */
+    public synchronized void cancel() {
+        mCancelled = true;
+        mHandler.removeMessages(MSG);
+    }
+
+    /**
+     * Start the countdown.
+     */
+    public synchronized void start() {
+        mCancelled = false;
+        mStopTimeInFuture = SystemClock.elapsedRealtime() + TIMEOUT_MS;
+        mHandler.sendMessage(mHandler.obtainMessage(MSG));
+    }
+
+    /**
+     * Called when the timer has expired.
+     */
+    public void onFinish() {
+        mClient.handleAuthStateTimerExpiry(mNanoAppId);
+    }
+
+    // Message type used to trigger the timer.
+    private static final int MSG = 1;
+
+    private class CountDownHandler extends Handler {
+
+        CountDownHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            synchronized (AuthStateDenialTimer.this) {
+                if (mCancelled) {
+                    return;
+                }
+                final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime();
+                if (millisLeft <= 0) {
+                    onFinish();
+                } else {
+                    sendMessageDelayed(obtainMessage(MSG), millisLeft);
+                }
+            }
+        }
+    };
+}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
index d3c853d..6249a06 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
@@ -17,9 +17,13 @@
 package com.android.server.location.contexthub;
 
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.hardware.location.ContextHubManager.AUTHORIZATION_DENIED;
+import static android.hardware.location.ContextHubManager.AUTHORIZATION_DENIED_GRACE_PERIOD;
+import static android.hardware.location.ContextHubManager.AUTHORIZATION_GRANTED;
 
 import android.Manifest;
 import android.annotation.Nullable;
+import android.app.AppOpsManager;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
@@ -30,19 +34,21 @@
 import android.hardware.location.ContextHubTransaction;
 import android.hardware.location.IContextHubClient;
 import android.hardware.location.IContextHubClientCallback;
+import android.hardware.location.IContextHubTransactionCallback;
 import android.hardware.location.NanoAppMessage;
+import android.hardware.location.NanoAppState;
 import android.os.Binder;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.util.Log;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.server.location.ClientBrokerProto;
 
-import java.util.ArrayList;
-import java.util.Collections;
 import java.util.Iterator;
-import java.util.Set;
+import java.util.List;
+import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Supplier;
@@ -52,14 +58,53 @@
  * notification callbacks. This class implements the IContextHubClient object, and the implemented
  * APIs must be thread-safe.
  *
+ * Additionally, this class is responsible for enforcing permissions usage and attribution are
+ * handled appropriately for a given client. In general, this works as follows:
+ *
+ * Client sending a message to a nanoapp:
+ * 1) When initially sending a message to nanoapps, clients are by default in a grace period state
+ *    which allows them to always send their first message to nanoapps. This is done to allow
+ *    clients (especially callback clients) to reset their conection to the nanoapp if they are
+ *    killed / restarted (e.g. following a permission revocation).
+ * 2) After the initial message is sent, a check of permissions state is performed. If the
+ *    client doesn't have permissions to communicate, it is placed into the denied grace period
+ *    state and notified so that it can clean up its communication before it is completely denied
+ *    access.
+ * 3) For subsequent messages, the auth state is checked synchronously and messages are denied if
+ *    the client is denied authorization
+ *
+ * Client receiving a message from a nanoapp:
+ * 1) If a nanoapp sends a message to the client, the authentication state is checked synchronously.
+ *    If there has been no message between the two before, the auth state is assumed granted.
+ * 2) The broker then checks that the client has all permissions the nanoapp requires and attributes
+ *    all permissions required to consume the message being sent. If both of those checks pass, then
+ *    the message is delivered. Otherwise, it's dropped.
+ *
+ * Client losing or gaining permissions (callback client):
+ * 1) Clients are killed when they lose permissions. This will cause callback clients to completely
+ *    disconnect from the service. When they are restarted, their initial message will still be
+ *    be allowed through and their permissions will be rechecked at that time.
+ * 2) If they gain a permission, the broker will notify them if that permission allows them to
+ *    communicate with a nanoapp again.
+ *
+ * Client losing or gaining permissions (PendingIntent client):
+ * 1) Unlike callback clients, PendingIntent clients are able to maintain their connection to the
+ *    service when they are killed. In their case, they will receive notifications of the broker
+ *    that they have been denied required permissions or gain required permissions.
+ *
  * TODO: Consider refactoring this class via inheritance
  *
  * @hide
  */
 public class ContextHubClientBroker extends IContextHubClient.Stub
-        implements IBinder.DeathRecipient {
+        implements IBinder.DeathRecipient, AppOpsManager.OnOpChangedListener {
     private static final String TAG = "ContextHubClientBroker";
 
+    /**
+     * Message used by noteOp when this client receives a message from a nanoapp.
+     */
+    private static final String RECEIVE_MSG_NOTE = "NanoappMessageDelivery ";
+
     /*
      * The context of the service.
      */
@@ -119,6 +164,26 @@
      */
     private final String mPackage;
 
+    /**
+     * The PID associated with this client.
+     */
+    private final int mPid;
+
+    /**
+     * The UID associated with this client.
+     */
+    private final int mUid;
+
+    /**
+     * Manager used for noting permissions usage of this broker.
+     */
+    private final AppOpsManager mAppOpsManager;
+
+    /**
+     * Manager used to queue transactions to the context hub.
+     */
+    private final ContextHubTransactionManager mTransactionManager;
+
     /*
      * True if a PendingIntent has been cancelled.
      */
@@ -130,11 +195,44 @@
     private final boolean mHasAccessContextHubPermission;
 
     /*
-     * The set of nanoapp IDs that represent the group of nanoapps this client has a messaging
-     * channel with, i.e. has sent or received messages from this particular nanoapp.
+     * Map containing all nanoapps this client has a messaging channel with and whether it is
+     * allowed to communicate over that channel. A channel is defined to have been opened if the
+     * client has sent or received messages from the particular nanoapp.
      */
-    private final Set<Long> mMessageChannelNanoappIdSet =
-            Collections.newSetFromMap(new ConcurrentHashMap<Long, Boolean>());
+    private final Map<Long, Integer> mMessageChannelNanoappIdMap =
+            new ConcurrentHashMap<Long, Integer>();
+
+    /**
+     * Map containing all nanoapps that have active auth state denial timers.
+     */
+    private final Map<Long, AuthStateDenialTimer> mNappToAuthTimerMap =
+            new ConcurrentHashMap<Long, AuthStateDenialTimer>();
+
+    /**
+     * Callback used to obtain the latest set of nanoapp permissions and verify this client has
+     * each nanoapps permissions granted.
+     */
+    private final IContextHubTransactionCallback mQueryPermsCallback =
+            new IContextHubTransactionCallback.Stub() {
+            @Override
+            public void onTransactionComplete(int result) {
+            }
+
+            @Override
+            public void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) {
+                if (result != ContextHubTransaction.RESULT_SUCCESS && nanoAppStateList != null) {
+                    Log.e(TAG, "Permissions query failed, but still received nanoapp state");
+                } else if (nanoAppStateList != null) {
+                    for (NanoAppState state : nanoAppStateList) {
+                        if (mMessageChannelNanoappIdMap.containsKey(state.getNanoAppId())) {
+                            List<String> permissions = state.getNanoAppPermissions();
+                            updateNanoAppAuthState(state.getNanoAppId(),
+                                    hasPermissions(permissions), false /* gracePeriodExpired */);
+                        }
+                    }
+                }
+            }
+        };
 
     /*
      * Helper class to manage registered PendingIntent requests from the client.
@@ -182,40 +280,57 @@
         }
     }
 
-    /* package */ ContextHubClientBroker(
-            Context context, IContextHubWrapper contextHubProxy,
+    private ContextHubClientBroker(Context context, IContextHubWrapper contextHubProxy,
             ContextHubClientManager clientManager, ContextHubInfo contextHubInfo,
-            short hostEndPointId, IContextHubClientCallback callback, String attributionTag) {
+            short hostEndPointId, IContextHubClientCallback callback, String attributionTag,
+            ContextHubTransactionManager transactionManager, PendingIntent pendingIntent,
+            long nanoAppId, String packageName) {
         mContext = context;
         mContextHubProxy = contextHubProxy;
         mClientManager = clientManager;
         mAttachedContextHubInfo = contextHubInfo;
         mHostEndPointId = hostEndPointId;
         mCallbackInterface = callback;
-        mPendingIntentRequest = new PendingIntentRequest();
-        mPackage = mContext.getPackageManager().getNameForUid(Binder.getCallingUid());
+        if (pendingIntent == null) {
+            mPendingIntentRequest = new PendingIntentRequest();
+        } else {
+            mPendingIntentRequest = new PendingIntentRequest(pendingIntent, nanoAppId);
+        }
+        mPackage = packageName;
         mAttributionTag = attributionTag;
+        mTransactionManager = transactionManager;
 
+        mPid = Binder.getCallingPid();
+        mUid = Binder.getCallingUid();
         mHasAccessContextHubPermission = context.checkCallingPermission(
                 Manifest.permission.ACCESS_CONTEXT_HUB) == PERMISSION_GRANTED;
+        mAppOpsManager = context.getSystemService(AppOpsManager.class);
+
+        startMonitoringOpChanges();
+    }
+
+    /* package */ ContextHubClientBroker(
+            Context context, IContextHubWrapper contextHubProxy,
+            ContextHubClientManager clientManager, ContextHubInfo contextHubInfo,
+            short hostEndPointId, IContextHubClientCallback callback, String attributionTag,
+            ContextHubTransactionManager transactionManager, String packageName) {
+        this(context, contextHubProxy, clientManager, contextHubInfo, hostEndPointId, callback,
+                attributionTag, transactionManager, null /* pendingIntent */, 0 /* nanoAppId */,
+                packageName);
     }
 
     /* package */ ContextHubClientBroker(
             Context context, IContextHubWrapper contextHubProxy,
             ContextHubClientManager clientManager, ContextHubInfo contextHubInfo,
             short hostEndPointId, PendingIntent pendingIntent, long nanoAppId,
-            String attributionTag) {
-        mContext = context;
-        mContextHubProxy = contextHubProxy;
-        mClientManager = clientManager;
-        mAttachedContextHubInfo = contextHubInfo;
-        mHostEndPointId = hostEndPointId;
-        mPendingIntentRequest = new PendingIntentRequest(pendingIntent, nanoAppId);
-        mPackage = pendingIntent.getCreatorPackage();
-        mAttributionTag = attributionTag;
+            String attributionTag, ContextHubTransactionManager transactionManager) {
+        this(context, contextHubProxy, clientManager, contextHubInfo, hostEndPointId,
+                null /* callback */, attributionTag, transactionManager, pendingIntent, nanoAppId,
+                pendingIntent.getCreatorPackage());
+    }
 
-        mHasAccessContextHubPermission = context.checkCallingPermission(
-                Manifest.permission.ACCESS_CONTEXT_HUB) == PERMISSION_GRANTED;
+    private void startMonitoringOpChanges() {
+        mAppOpsManager.startWatchingMode(AppOpsManager.OP_NONE, mPackage, this);
     }
 
     /**
@@ -229,18 +344,44 @@
     public int sendMessageToNanoApp(NanoAppMessage message) {
         ContextHubServiceUtil.checkPermissions(mContext);
 
+        int authState;
+        synchronized (mMessageChannelNanoappIdMap) {
+            // Default to the granted auth state. The true auth state will be checked async if it's
+            // not denied.
+            authState = mMessageChannelNanoappIdMap.getOrDefault(
+                    message.getNanoAppId(), AUTHORIZATION_GRANTED);
+            if (authState == AUTHORIZATION_DENIED) {
+                return ContextHubTransaction.RESULT_FAILED_PERMISSION_DENIED;
+            }
+        }
+
         int result;
         if (isRegistered()) {
-            mMessageChannelNanoappIdSet.add(message.getNanoAppId());
+            // Even though the auth state is currently not denied, query the nanoapp permissions
+            // async and verify that the host app currently holds all the requisite permissions.
+            // This can't be done synchronously due to the async query that needs to be performed to
+            // obtain the nanoapp permissions.
+            boolean initialNanoappMessage = false;
+            synchronized (mMessageChannelNanoappIdMap) {
+                if (mMessageChannelNanoappIdMap.get(message.getNanoAppId()) == null) {
+                    mMessageChannelNanoappIdMap.put(message.getNanoAppId(), AUTHORIZATION_GRANTED);
+                    initialNanoappMessage = true;
+                }
+            }
+
+            if (initialNanoappMessage) {
+                // Only check permissions the first time a nanoapp is queried since nanoapp
+                // permissions don't currently change at runtime. If the host permission changes
+                // later, that'll be checked by onOpChanged.
+                checkNanoappPermsAsync();
+            }
+
             ContextHubMsg messageToNanoApp =
                     ContextHubServiceUtil.createHidlContextHubMessage(mHostEndPointId, message);
 
             int contextHubId = mAttachedContextHubInfo.getId();
             try {
-                // TODO(166846988): Fill in host permissions before sending a message.
-                result = mContextHubProxy.sendMessageToHub(
-                        contextHubId, messageToNanoApp,
-                        new ArrayList<String>() /* hostPermissions */);
+                result = mContextHubProxy.sendMessageToHub(contextHubId, messageToNanoApp);
             } catch (RemoteException e) {
                 Log.e(TAG, "RemoteException in sendMessageToNanoApp (target hub ID = "
                         + contextHubId + ")", e);
@@ -275,6 +416,19 @@
         onClientExit();
     }
 
+    @Override
+    public void onOpChanged(String op, String packageName) {
+        if (packageName.equals(mPackage)) {
+            if (!mMessageChannelNanoappIdMap.isEmpty()) {
+                checkNanoappPermsAsync();
+            }
+        }
+    }
+
+    /* package */ String getPackageName() {
+        return mPackage;
+    }
+
     /**
      * Used to override the attribution tag with a newer value if a PendingIntent broker is
      * retrieved.
@@ -308,15 +462,39 @@
      * Sends a message to the client associated with this object.
      *
      * @param message the message that came from a nanoapp
+     * @param nanoappPermissions permissions required to communicate with the nanoapp sending this
+     * message
+     * @param messagePermissions permissions required to consume the message being delivered. These
+     * permissions are what will be attributed to the client through noteOp.
      */
-    /* package */ void sendMessageToClient(NanoAppMessage message) {
-        mMessageChannelNanoappIdSet.add(message.getNanoAppId());
+    /* package */ void sendMessageToClient(
+            NanoAppMessage message, List<String> nanoappPermissions,
+            List<String> messagePermissions) {
+        long nanoAppId = message.getNanoAppId();
+
+        int authState = mMessageChannelNanoappIdMap.getOrDefault(nanoAppId, AUTHORIZATION_GRANTED);
+
+        // If in the grace period, the host may not receive any messages containing permissions
+        // covered data.
+        if (authState == AUTHORIZATION_DENIED_GRACE_PERIOD && !messagePermissions.isEmpty()) {
+            Log.e(TAG, "Dropping message from " + Long.toHexString(nanoAppId) + ". " + mPackage
+                    + " in grace period and napp msg has permissions");
+            return;
+        }
+
+        if (authState == AUTHORIZATION_DENIED || !hasPermissions(nanoappPermissions)
+                || !notePermissions(messagePermissions, RECEIVE_MSG_NOTE + nanoAppId)) {
+            Log.e(TAG, "Dropping message from " + Long.toHexString(nanoAppId) + ". " + mPackage
+                    + " doesn't have permission");
+            return;
+        }
+
         invokeCallback(callback -> callback.onMessageFromNanoApp(message));
 
         Supplier<Intent> supplier =
-                () -> createIntent(ContextHubManager.EVENT_NANOAPP_MESSAGE, message.getNanoAppId())
+                () -> createIntent(ContextHubManager.EVENT_NANOAPP_MESSAGE, nanoAppId)
                         .putExtra(ContextHubManager.EXTRA_MESSAGE, message);
-        sendPendingIntent(supplier, message.getNanoAppId());
+        sendPendingIntent(supplier, nanoAppId);
     }
 
     /**
@@ -325,6 +503,10 @@
      * @param nanoAppId the ID of the nanoapp that was loaded.
      */
     /* package */ void onNanoAppLoaded(long nanoAppId) {
+        // Check the latest state to see if the loaded nanoapp's permissions changed such that the
+        // host app can communicate with it again.
+        checkNanoappPermsAsync();
+
         invokeCallback(callback -> callback.onNanoAppLoaded(nanoAppId));
         sendPendingIntent(
                 () -> createIntent(ContextHubManager.EVENT_NANOAPP_LOADED, nanoAppId), nanoAppId);
@@ -392,6 +574,43 @@
     }
 
     /**
+     * Checks that this client has all of the provided permissions.
+     *
+     * @param permissions list of permissions to check
+     * @return true if the client has all of the permissions granted
+     */
+    /* package */ boolean hasPermissions(List<String> permissions) {
+        for (String permission : permissions) {
+            if (mContext.checkPermission(permission, mPid, mUid) != PERMISSION_GRANTED) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Attributes the provided permissions to the package of this client.
+     *
+     * @param permissions list of permissions covering data the client is about to receive
+     * @param noteMessage message that should be noted alongside permissions attribution to
+     * facilitate debugging
+     * @return true if client has ability to use all of the provided permissions
+     */
+    /* package */ boolean notePermissions(List<String> permissions, String noteMessage) {
+        for (String permission : permissions) {
+            int opCode = mAppOpsManager.permissionToOpCode(permission);
+            if (opCode != AppOpsManager.OP_NONE) {
+                if (mAppOpsManager.noteOp(opCode, mUid, mPackage, mAttributionTag, noteMessage)
+                        != AppOpsManager.MODE_ALLOWED) {
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    /**
      * @return true if the client is a PendingIntent client that has been cancelled.
      */
     /* package */ boolean isPendingIntentCancelled() {
@@ -399,6 +618,101 @@
     }
 
     /**
+     * Handles timer expiry for a client whose auth state with a nanoapp was previously in the grace
+     * period.
+     */
+    /* package */ void handleAuthStateTimerExpiry(long nanoAppId) {
+        AuthStateDenialTimer timer;
+        synchronized (mMessageChannelNanoappIdMap) {
+            timer = mNappToAuthTimerMap.remove(nanoAppId);
+        }
+
+        if (timer != null) {
+            updateNanoAppAuthState(
+                    nanoAppId, false /* hasPermissions */, true /* gracePeriodExpired */);
+        }
+    }
+
+    /**
+     * Verifies this client has the permissions to communicate with all of the nanoapps it has
+     * communicated with in the past.
+     */
+    private void checkNanoappPermsAsync() {
+        ContextHubServiceTransaction transaction = mTransactionManager.createQueryTransaction(
+                mAttachedContextHubInfo.getId(), mQueryPermsCallback, mPackage);
+        mTransactionManager.addTransaction(transaction);
+    }
+
+    /**
+     * Updates the latest authentication state for this client to be able to communicate with the
+     * given nanoapp.
+     */
+    private void updateNanoAppAuthState(
+            long nanoAppId, boolean hasPermissions, boolean gracePeriodExpired) {
+        updateNanoAppAuthState(
+                nanoAppId, hasPermissions, gracePeriodExpired, false /* forceDenied */);
+    }
+
+    /* package */ void updateNanoAppAuthState(
+            long nanoAppId, boolean hasPermissions, boolean gracePeriodExpired,
+            boolean forceDenied) {
+        int curAuthState;
+        int newAuthState;
+        synchronized (mMessageChannelNanoappIdMap) {
+            curAuthState = mMessageChannelNanoappIdMap.getOrDefault(
+                    nanoAppId, AUTHORIZATION_GRANTED);
+            newAuthState = curAuthState;
+            // The below logic ensures that only the following transitions are possible:
+            // GRANTED -> DENIED_GRACE_PERIOD only if permissions have been lost
+            // DENIED_GRACE_PERIOD -> DENIED only if the grace period expires
+            // DENIED/DENIED_GRACE_PERIOD -> GRANTED only if permissions are granted again
+            // any state -> DENIED if "forceDenied" is true
+            if (forceDenied) {
+                newAuthState = AUTHORIZATION_DENIED;
+            } else if (gracePeriodExpired) {
+                if (curAuthState == AUTHORIZATION_DENIED_GRACE_PERIOD) {
+                    newAuthState = AUTHORIZATION_DENIED;
+                }
+            } else {
+                if (curAuthState == AUTHORIZATION_GRANTED && !hasPermissions) {
+                    newAuthState = AUTHORIZATION_DENIED_GRACE_PERIOD;
+                } else if (curAuthState != AUTHORIZATION_GRANTED && hasPermissions) {
+                    newAuthState = AUTHORIZATION_GRANTED;
+                }
+            }
+
+            if (newAuthState != AUTHORIZATION_DENIED_GRACE_PERIOD) {
+                AuthStateDenialTimer timer = mNappToAuthTimerMap.remove(nanoAppId);
+                if (timer != null) {
+                    timer.cancel();
+                }
+            } else if (curAuthState == AUTHORIZATION_GRANTED) {
+                AuthStateDenialTimer timer =
+                        new AuthStateDenialTimer(this, nanoAppId, Looper.getMainLooper());
+                mNappToAuthTimerMap.put(nanoAppId, timer);
+                timer.start();
+            }
+
+            if (curAuthState != newAuthState) {
+                mMessageChannelNanoappIdMap.put(nanoAppId, newAuthState);
+            }
+        }
+        if (curAuthState != newAuthState) {
+            // Don't send the callback in the synchronized block or it could end up in a deadlock.
+            sendAuthStateCallback(nanoAppId, newAuthState);
+        }
+    }
+
+    private void sendAuthStateCallback(long nanoAppId, int authState) {
+        invokeCallback(callback -> callback.onClientAuthorizationChanged(nanoAppId, authState));
+
+        Supplier<Intent> supplier =
+                () -> createIntent(ContextHubManager.EVENT_CLIENT_AUTHORIZATION, nanoAppId)
+                        .putExtra(ContextHubManager.EXTRA_CLIENT_AUTHORIZATION_STATE, authState);
+        sendPendingIntent(supplier, nanoAppId);
+    }
+
+    /**
      * Helper function to invoke a specified client callback, if the connection is open.
      *
      * @param consumer the consumer specifying the callback to invoke
@@ -507,6 +821,20 @@
             mClientManager.unregisterClient(mHostEndPointId);
             mRegistered = false;
         }
+        mAppOpsManager.stopWatchingMode(this);
+    }
+
+    private String authStateToString(@ContextHubManager.AuthorizationState int state) {
+        switch (state) {
+            case AUTHORIZATION_DENIED:
+                return "DENIED";
+            case AUTHORIZATION_DENIED_GRACE_PERIOD:
+                return "DENIED_GRACE_PERIOD";
+            case AUTHORIZATION_GRANTED:
+                return "GRANTED";
+            default:
+                return "UNKNOWN";
+        }
     }
 
     /**
@@ -545,11 +873,14 @@
         } else {
             out += "package: " + mPackage;
         }
-        if (mMessageChannelNanoappIdSet.size() > 0) {
+        if (mMessageChannelNanoappIdMap.size() > 0) {
             out += " messageChannelNanoappSet: (";
-            Iterator<Long> it = mMessageChannelNanoappIdSet.iterator();
+            Iterator<Map.Entry<Long, Integer>> it =
+                    mMessageChannelNanoappIdMap.entrySet().iterator();
             while (it.hasNext()) {
-                out += "0x" + Long.toHexString(it.next());
+                Map.Entry<Long, Integer> entry = it.next();
+                out += "0x" + Long.toHexString(entry.getKey()) + " auth state: "
+                        + authStateToString(entry.getValue());
                 if (it.hasNext()) {
                     out += ",";
                 }
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java
index 0351edb..e3522f6 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java
@@ -36,6 +36,7 @@
 import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.Iterator;
+import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Consumer;
 
@@ -136,8 +137,7 @@
         }
     }
 
-    /* package */ ContextHubClientManager(
-            Context context, IContextHubWrapper contextHubProxy) {
+    /* package */ ContextHubClientManager(Context context, IContextHubWrapper contextHubProxy) {
         mContext = context;
         mContextHubProxy = contextHubProxy;
     }
@@ -155,13 +155,15 @@
      */
     /* package */ IContextHubClient registerClient(
             ContextHubInfo contextHubInfo, IContextHubClientCallback clientCallback,
-            String attributionTag) {
+            String attributionTag, ContextHubTransactionManager transactionManager,
+            String packageName) {
         ContextHubClientBroker broker;
         synchronized (this) {
             short hostEndPointId = getHostEndPointId();
             broker = new ContextHubClientBroker(
                     mContext, mContextHubProxy, this /* clientManager */, contextHubInfo,
-                    hostEndPointId, clientCallback, attributionTag);
+                    hostEndPointId, clientCallback, attributionTag, transactionManager,
+                    packageName);
             mHostEndPointIdToClientMap.put(hostEndPointId, broker);
             mRegistrationRecordDeque.add(
                     new RegistrationRecord(broker.toString(), ACTION_REGISTERED));
@@ -194,7 +196,7 @@
      */
     /* package */ IContextHubClient registerClient(
             ContextHubInfo contextHubInfo, PendingIntent pendingIntent, long nanoAppId,
-            String attributionTag) {
+            String attributionTag, ContextHubTransactionManager transactionManager) {
         ContextHubClientBroker broker;
         String registerString = "Regenerated";
         synchronized (this) {
@@ -204,7 +206,8 @@
                 short hostEndPointId = getHostEndPointId();
                 broker = new ContextHubClientBroker(
                         mContext, mContextHubProxy, this /* clientManager */, contextHubInfo,
-                        hostEndPointId, pendingIntent, nanoAppId, attributionTag);
+                        hostEndPointId, pendingIntent, nanoAppId, attributionTag,
+                        transactionManager);
                 mHostEndPointIdToClientMap.put(hostEndPointId, broker);
                 registerString = "Registered";
                 mRegistrationRecordDeque.add(
@@ -224,9 +227,14 @@
      * Handles a message sent from a nanoapp.
      *
      * @param contextHubId the ID of the hub where the nanoapp sent the message from
-     * @param message      the message send by a nanoapp
+     * @param message the message send by a nanoapp
+     * @param nanoappPermissions the set of permissions the nanoapp holds
+     * @param messagePermissions the set of permissions that should be used for attributing
+     * permissions when this message is consumed by a client
      */
-    /* package */ void onMessageFromNanoApp(int contextHubId, ContextHubMsg message) {
+    /* package */ void onMessageFromNanoApp(
+            int contextHubId, ContextHubMsg message, List<String> nanoappPermissions,
+            List<String> messagePermissions) {
         NanoAppMessage clientMessage = ContextHubServiceUtil.createNanoAppMessage(message);
 
         if (DEBUG_LOG_ENABLED) {
@@ -234,11 +242,19 @@
         }
 
         if (clientMessage.isBroadcastMessage()) {
-            broadcastMessage(contextHubId, clientMessage);
+            // Broadcast messages shouldn't be sent with any permissions tagged per CHRE API
+            // requirements.
+            if (!messagePermissions.isEmpty()) {
+                Log.wtf(TAG, "Received broadcast message with permissions from " + message.appName);
+            }
+
+            broadcastMessage(
+                    contextHubId, clientMessage, nanoappPermissions, messagePermissions);
         } else {
             ContextHubClientBroker proxy = mHostEndPointIdToClientMap.get(message.hostEndPoint);
             if (proxy != null) {
-                proxy.sendMessageToClient(clientMessage);
+                proxy.sendMessageToClient(
+                        clientMessage, nanoappPermissions, messagePermissions);
             } else {
                 Log.e(TAG, "Cannot send message to unregistered client (host endpoint ID = "
                         + message.hostEndPoint + ")");
@@ -303,6 +319,21 @@
     }
 
     /**
+     * Runs a command for each client that is attached to a hub with the given ID.
+     *
+     * @param contextHubId the ID of the hub
+     * @param callback     the command to invoke for the client
+     */
+    /* package */ void forEachClientOfHub(
+            int contextHubId, Consumer<ContextHubClientBroker> callback) {
+        for (ContextHubClientBroker broker : mHostEndPointIdToClientMap.values()) {
+            if (broker.getAttachedContextHubId() == contextHubId) {
+                callback.accept(broker);
+            }
+        }
+    }
+
+    /**
      * Returns an available host endpoint ID.
      *
      * @returns an available host endpoint ID
@@ -333,22 +364,12 @@
      * @param contextHubId the ID of the hub where the nanoapp sent the message from
      * @param message      the message send by a nanoapp
      */
-    private void broadcastMessage(int contextHubId, NanoAppMessage message) {
-        forEachClientOfHub(contextHubId, client -> client.sendMessageToClient(message));
-    }
-
-    /**
-     * Runs a command for each client that is attached to a hub with the given ID.
-     *
-     * @param contextHubId the ID of the hub
-     * @param callback     the command to invoke for the client
-     */
-    private void forEachClientOfHub(int contextHubId, Consumer<ContextHubClientBroker> callback) {
-        for (ContextHubClientBroker broker : mHostEndPointIdToClientMap.values()) {
-            if (broker.getAttachedContextHubId() == contextHubId) {
-                callback.accept(broker);
-            }
-        }
+    private void broadcastMessage(
+            int contextHubId, NanoAppMessage message, List<String> nanoappPermissions,
+            List<String> messagePermissions) {
+        forEachClientOfHub(contextHubId,
+                client -> client.sendMessageToClient(
+                        message, nanoappPermissions, messagePermissions));
     }
 
     /**
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 2eafe6a..0737db7 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -50,6 +50,8 @@
 import android.os.Binder;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Log;
@@ -64,6 +66,7 @@
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -98,6 +101,7 @@
     private final Context mContext;
 
     private final Map<Integer, ContextHubInfo> mContextHubIdToInfoMap;
+    private final List<String> mSupportedContextHubPerms;
     private final List<ContextHubInfo> mContextHubInfoList;
     private final RemoteCallbackList<IContextHubCallback> mCallbacksList =
             new RemoteCallbackList<>();
@@ -140,7 +144,7 @@
         public void handleClientMsg(ContextHubMsg message) {
             handleClientMessageCallback(mContextHubId, message,
                     Collections.emptyList() /* nanoappPermissions */,
-                    Collections.emptyList() /* messageContentPermissions */);
+                    Collections.emptyList() /* messagePermissions */);
         }
 
         @Override
@@ -167,9 +171,9 @@
 
         @Override
         public void handleClientMsg_1_2(android.hardware.contexthub.V1_2.ContextHubMsg message,
-                ArrayList<String> messageContentPermissions) {
+                ArrayList<String> messagePermissions) {
             handleClientMessageCallback(mContextHubId, message.msg_1_0, message.permissions,
-                    messageContentPermissions);
+                    messagePermissions);
         }
 
         @Override
@@ -187,14 +191,11 @@
             mClientManager = null;
             mDefaultClientMap = Collections.emptyMap();
             mContextHubIdToInfoMap = Collections.emptyMap();
+            mSupportedContextHubPerms = Collections.emptyList();
             mContextHubInfoList = Collections.emptyList();
             return;
         }
 
-        mClientManager = new ContextHubClientManager(mContext, mContextHubWrapper);
-        mTransactionManager = new ContextHubTransactionManager(
-                mContextHubWrapper.getHub(), mClientManager, mNanoAppStateManager);
-
         Pair<List<ContextHub>, List<String>> hubInfo;
         try {
             hubInfo = mContextHubWrapper.getHubs();
@@ -202,16 +203,21 @@
             Log.e(TAG, "RemoteException while getting Context Hub info", e);
             hubInfo = new Pair(Collections.emptyList(), Collections.emptyList());
         }
+
         mContextHubIdToInfoMap = Collections.unmodifiableMap(
                 ContextHubServiceUtil.createContextHubInfoMap(hubInfo.first));
+        mSupportedContextHubPerms = hubInfo.second;
         mContextHubInfoList = new ArrayList<>(mContextHubIdToInfoMap.values());
+        mClientManager = new ContextHubClientManager(mContext, mContextHubWrapper);
+        mTransactionManager = new ContextHubTransactionManager(
+                mContextHubWrapper.getHub(), mClientManager, mNanoAppStateManager);
 
         HashMap<Integer, IContextHubClient> defaultClientMap = new HashMap<>();
         for (int contextHubId : mContextHubIdToInfoMap.keySet()) {
             ContextHubInfo contextHubInfo = mContextHubIdToInfoMap.get(contextHubId);
             IContextHubClient client = mClientManager.registerClient(
                     contextHubInfo, createDefaultClientCallback(contextHubId),
-                    null /* attributionTag */);
+                    null /* attributionTag */, mTransactionManager, mContext.getPackageName());
             defaultClientMap.put(contextHubId, client);
 
             try {
@@ -362,6 +368,12 @@
     }
 
     @Override
+    public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+            String[] args, ShellCallback callback, ResultReceiver result) {
+        new ContextHubShellCommand(mContext, this).exec(this, in, out, err, args, callback, result);
+    }
+
+    @Override
     public int registerCallback(IContextHubCallback callback) throws RemoteException {
         checkPermissions();
         mCallbacksList.register(callback);
@@ -611,13 +623,15 @@
     /**
      * Handles a unicast or broadcast message from a nanoapp.
      *
-     * @param contextHubId the ID of the hub the message came from
-     * @param message      the message contents
+     * @param contextHubId   the ID of the hub the message came from
+     * @param message        the message contents
+     * @param reqPermissions the permissions required to consume this message
      */
     private void handleClientMessageCallback(
             int contextHubId, ContextHubMsg message, List<String> nanoappPermissions,
-            List<String> messageContentPermissions) {
-        mClientManager.onMessageFromNanoApp(contextHubId, message);
+            List<String> messagePermissions) {
+        mClientManager.onMessageFromNanoApp(
+                contextHubId, message, nanoappPermissions, messagePermissions);
     }
 
     /**
@@ -723,6 +737,7 @@
      * @param contextHubId   the ID of the hub this client is attached to
      * @param clientCallback the client interface to register with the service
      * @param attributionTag an optional attribution tag within the given package
+     * @param packageName    the name of the package creating this client
      * @return the generated client interface, null if registration was unsuccessful
      * @throws IllegalArgumentException if contextHubId is not a valid ID
      * @throws IllegalStateException    if max number of clients have already registered
@@ -731,7 +746,7 @@
     @Override
     public IContextHubClient createClient(
             int contextHubId, IContextHubClientCallback clientCallback,
-            @Nullable String attributionTag) throws RemoteException {
+            @Nullable String attributionTag, String packageName) throws RemoteException {
         checkPermissions();
         if (!isValidContextHubId(contextHubId)) {
             throw new IllegalArgumentException("Invalid context hub ID " + contextHubId);
@@ -741,7 +756,8 @@
         }
 
         ContextHubInfo contextHubInfo = mContextHubIdToInfoMap.get(contextHubId);
-        return mClientManager.registerClient(contextHubInfo, clientCallback, attributionTag);
+        return mClientManager.registerClient(
+                contextHubInfo, clientCallback, attributionTag, mTransactionManager, packageName);
     }
 
     /**
@@ -766,7 +782,7 @@
 
         ContextHubInfo contextHubInfo = mContextHubIdToInfoMap.get(contextHubId);
         return mClientManager.registerClient(
-                contextHubInfo, pendingIntent, nanoAppId, attributionTag);
+                contextHubInfo, pendingIntent, nanoAppId, attributionTag, mTransactionManager);
     }
 
     /**
@@ -907,6 +923,8 @@
         for (ContextHubInfo hubInfo : mContextHubIdToInfoMap.values()) {
             pw.println(hubInfo);
         }
+        pw.println("Supported permissions: "
+                + Arrays.toString(mSupportedContextHubPerms.toArray()));
         pw.println("");
         pw.println("=================== NANOAPPS ====================");
         // Dump nanoAppHash
@@ -923,6 +941,16 @@
         // dump eventLog
     }
 
+    /* package */ void denyClientAuthState(int contextHubId, String packageName, long nanoAppId) {
+        mClientManager.forEachClientOfHub(contextHubId, client -> {
+            if (client.getPackageName().equals(packageName)) {
+                client.updateNanoAppAuthState(
+                        nanoAppId, false /* hasPermissions */, false /* gracePeriodExpired */,
+                        true /* forceDenied */);
+            }
+        });
+    }
+
     private void dump(ProtoOutputStream proto) {
         mContextHubIdToInfoMap.values().forEach(hubInfo -> {
             long token = proto.start(ContextHubServiceProto.CONTEXT_HUB_INFO);
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubShellCommand.java b/services/core/java/com/android/server/location/contexthub/ContextHubShellCommand.java
new file mode 100644
index 0000000..5ec85e6
--- /dev/null
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubShellCommand.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.location.contexthub;
+
+import android.content.Context;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+/**
+ * ShellCommands for ContextHubService.
+ *
+ * Use with {@code adb shell cmd contexthub ...}.
+ *
+ * @hide
+ */
+public class ContextHubShellCommand extends ShellCommand {
+
+    // Internal service impl -- must perform security checks before touching.
+    private final ContextHubService mInternal;
+
+    public ContextHubShellCommand(Context context, ContextHubService service) {
+        mInternal = service;
+
+        context.enforceCallingOrSelfPermission(
+                android.Manifest.permission.ACCESS_CONTEXT_HUB, "ContextHubShellCommand");
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        if ("deny".equals(cmd)) {
+            return runDisableAuth();
+        }
+
+        return handleDefaultCommands(cmd);
+    }
+
+    private int runDisableAuth() {
+        int contextHubId = Integer.decode(getNextArgRequired());
+        String packageName = getNextArgRequired();
+        long nanoAppId = Long.decode(getNextArgRequired());
+
+        mInternal.denyClientAuthState(contextHubId, packageName, nanoAppId);
+        return 0;
+    }
+
+    @Override
+    public void onHelp() {
+        PrintWriter pw = getOutPrintWriter();
+        pw.println("ContextHub commands:");
+        pw.println("  help");
+        pw.println("      Print this help text.");
+        pw.println("  deny [contextHubId] [packageName] [nanoAppId]");
+        pw.println("    Immediately transitions the package's authentication state to denied so");
+        pw.println("    can no longer communciate with the nanoapp.");
+    }
+}
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index c1d63dd..3a5c220 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -106,9 +106,8 @@
     /**
      * Calls the appropriate sendMessageToHub function depending on the HAL version.
      */
-    public abstract int sendMessageToHub(
-            int hubId, android.hardware.contexthub.V1_0.ContextHubMsg message,
-            ArrayList<String> hostPermissions) throws RemoteException;
+    public abstract int sendMessageToHub(int hubId,
+            android.hardware.contexthub.V1_0.ContextHubMsg message) throws RemoteException;
 
     /**
      * @return A valid instance of Contexthub HAL 1.0.
@@ -181,9 +180,8 @@
             mHub.registerCallback(hubId, callback);
         }
 
-        public int sendMessageToHub(
-                int hubId, android.hardware.contexthub.V1_0.ContextHubMsg message,
-                ArrayList<String> hostPermissions) throws RemoteException {
+        public int sendMessageToHub(int hubId,
+                android.hardware.contexthub.V1_0.ContextHubMsg message) throws RemoteException {
             return mHub.sendMessageToHub(hubId, message);
         }
 
@@ -236,9 +234,8 @@
             mHub.registerCallback(hubId, callback);
         }
 
-        public int sendMessageToHub(
-                int hubId, android.hardware.contexthub.V1_0.ContextHubMsg message,
-                ArrayList<String> hostPermissions) throws RemoteException {
+        public int sendMessageToHub(int hubId,
+                android.hardware.contexthub.V1_0.ContextHubMsg message) throws RemoteException {
             return mHub.sendMessageToHub(hubId, message);
         }
 
@@ -307,13 +304,11 @@
             mHub.registerCallback_1_2(hubId, callback);
         }
 
-        public int sendMessageToHub(
-                int hubId, android.hardware.contexthub.V1_0.ContextHubMsg message,
-                ArrayList<String> hostPermissions) throws RemoteException {
+        public int sendMessageToHub(int hubId,
+                android.hardware.contexthub.V1_0.ContextHubMsg message) throws RemoteException {
             android.hardware.contexthub.V1_2.ContextHubMsg newMessage =
                     new android.hardware.contexthub.V1_2.ContextHubMsg();
             newMessage.msg_1_0 = message;
-            newMessage.permissions = hostPermissions;
             return mHub.sendMessageToHub_1_2(hubId, newMessage);
         }
 
diff --git a/services/core/java/com/android/server/location/injector/SystemDeviceIdleHelper.java b/services/core/java/com/android/server/location/injector/SystemDeviceIdleHelper.java
index 6a89079..736b654 100644
--- a/services/core/java/com/android/server/location/injector/SystemDeviceIdleHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemDeviceIdleHelper.java
@@ -21,50 +21,80 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.os.Binder;
 import android.os.PowerManager;
 
+import com.android.internal.util.Preconditions;
 import com.android.server.FgThread;
 
+import java.util.Objects;
+
 /**
  * Provides accessors and listeners for device stationary state.
  */
 public class SystemDeviceIdleHelper extends DeviceIdleHelper {
 
     private final Context mContext;
-    private final PowerManager mPowerManager;
 
+    private PowerManager mPowerManager;
+
+    private boolean mSystemReady;
+    private boolean mRegistrationRequired;
     private @Nullable BroadcastReceiver mReceiver;
 
     public SystemDeviceIdleHelper(Context context) {
         mContext = context;
-        mPowerManager = context.getSystemService(PowerManager.class);
+    }
+
+    public synchronized void onSystemReady() {
+        mSystemReady = true;
+        mPowerManager = Objects.requireNonNull(mContext.getSystemService(PowerManager.class));
+        onRegistrationStateChanged();
     }
 
     @Override
-    protected void registerInternal() {
-        if (mReceiver == null) {
-            mReceiver = new BroadcastReceiver() {
-                @Override
-                public void onReceive(Context context, Intent intent) {
-                    notifyDeviceIdleChanged();
-                }
-            };
+    protected synchronized void registerInternal() {
+        mRegistrationRequired = true;
+        onRegistrationStateChanged();
+    }
 
-            mContext.registerReceiver(mReceiver,
-                    new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED), null,
-                    FgThread.getHandler());
+    @Override
+    protected synchronized void unregisterInternal() {
+        mRegistrationRequired = false;
+        onRegistrationStateChanged();
+    }
+
+    private void onRegistrationStateChanged() {
+        if (!mSystemReady) {
+            return;
         }
-    }
 
-    @Override
-    protected void unregisterInternal() {
-        if (mReceiver != null) {
-            mContext.unregisterReceiver(mReceiver);
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            if (mRegistrationRequired && mReceiver == null) {
+                BroadcastReceiver receiver = new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        notifyDeviceIdleChanged();
+                    }
+                };
+                mContext.registerReceiver(receiver,
+                        new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED), null,
+                        FgThread.getHandler());
+                mReceiver = receiver;
+            } else if (!mRegistrationRequired && mReceiver != null) {
+                BroadcastReceiver receiver = mReceiver;
+                mReceiver = null;
+                mContext.unregisterReceiver(receiver);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
         }
     }
 
     @Override
     public boolean isDeviceIdle() {
+        Preconditions.checkState(mPowerManager != null);
         return mPowerManager.isDeviceIdleMode();
     }
 }
diff --git a/services/core/java/com/android/server/location/injector/SystemDeviceStationaryHelper.java b/services/core/java/com/android/server/location/injector/SystemDeviceStationaryHelper.java
index 6f0e681..9874ecf 100644
--- a/services/core/java/com/android/server/location/injector/SystemDeviceStationaryHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemDeviceStationaryHelper.java
@@ -16,6 +16,9 @@
 
 package com.android.server.location.injector;
 
+import android.os.Binder;
+
+import com.android.internal.util.Preconditions;
 import com.android.server.DeviceIdleInternal;
 import com.android.server.LocalServices;
 
@@ -26,19 +29,35 @@
  */
 public class SystemDeviceStationaryHelper extends DeviceStationaryHelper {
 
-    private final DeviceIdleInternal mDeviceIdle;
+    private DeviceIdleInternal mDeviceIdle;
 
-    public SystemDeviceStationaryHelper() {
+    public SystemDeviceStationaryHelper() {}
+
+    public void onSystemReady() {
         mDeviceIdle = Objects.requireNonNull(LocalServices.getService(DeviceIdleInternal.class));
     }
 
     @Override
     public void addListener(DeviceIdleInternal.StationaryListener listener) {
-        mDeviceIdle.registerStationaryListener(listener);
+        Preconditions.checkState(mDeviceIdle != null);
+
+        long identity = Binder.clearCallingIdentity();
+        try {
+            mDeviceIdle.registerStationaryListener(listener);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 
     @Override
     public void removeListener(DeviceIdleInternal.StationaryListener listener) {
-        mDeviceIdle.unregisterStationaryListener(listener);
+        Preconditions.checkState(mDeviceIdle != null);
+
+        long identity = Binder.clearCallingIdentity();
+        try {
+            mDeviceIdle.unregisterStationaryListener(listener);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/location/injector/SystemScreenInteractiveHelper.java b/services/core/java/com/android/server/location/injector/SystemScreenInteractiveHelper.java
index 0d7bcb0..03ade5f 100644
--- a/services/core/java/com/android/server/location/injector/SystemScreenInteractiveHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemScreenInteractiveHelper.java
@@ -68,7 +68,7 @@
         mReady = true;
     }
 
-    private void onScreenInteractiveChanged(boolean interactive) {
+    void onScreenInteractiveChanged(boolean interactive) {
         if (interactive == mIsInteractive) {
             return;
         }
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 685e9e6..8da2d67 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -2902,11 +2902,8 @@
         FingerprintManager mFingerprintManager = mInjector.getFingerprintManager();
         if (mFingerprintManager != null && mFingerprintManager.isHardwareDetected()) {
             if (mFingerprintManager.hasEnrolledFingerprints(userId)) {
-                CountDownLatch latch = new CountDownLatch(1);
-                // For the purposes of M and N, groupId is the same as userId.
-                Fingerprint finger = new Fingerprint(null, userId, 0, 0);
-                mFingerprintManager.remove(finger, userId,
-                        fingerprintManagerRemovalCallback(latch));
+                final CountDownLatch latch = new CountDownLatch(1);
+                mFingerprintManager.removeAll(userId, fingerprintManagerRemovalCallback(latch));
                 try {
                     latch.await(10000, TimeUnit.MILLISECONDS);
                 } catch (InterruptedException e) {
@@ -2920,9 +2917,8 @@
         FaceManager mFaceManager = mInjector.getFaceManager();
         if (mFaceManager != null && mFaceManager.isHardwareDetected()) {
             if (mFaceManager.hasEnrolledTemplates(userId)) {
-                CountDownLatch latch = new CountDownLatch(1);
-                Face face = new Face(null, 0, 0);
-                mFaceManager.remove(face, userId, faceManagerRemovalCallback(latch));
+                final CountDownLatch latch = new CountDownLatch(1);
+                mFaceManager.removeAll(userId, faceManagerRemovalCallback(latch));
                 try {
                     latch.await(10000, TimeUnit.MILLISECONDS);
                 } catch (InterruptedException e) {
@@ -2936,10 +2932,8 @@
             CountDownLatch latch) {
         return new FingerprintManager.RemovalCallback() {
             @Override
-            public void onRemovalError(Fingerprint fp, int errMsgId, CharSequence err) {
-                Slog.e(TAG, String.format(
-                        "Can't remove fingerprint %d in group %d. Reason: %s",
-                        fp.getBiometricId(), fp.getGroupId(), err));
+            public void onRemovalError(@Nullable Fingerprint fp, int errMsgId, CharSequence err) {
+                Slog.e(TAG, "Unable to remove fingerprint, error: " + err);
                 latch.countDown();
             }
 
@@ -2955,9 +2949,8 @@
     private FaceManager.RemovalCallback faceManagerRemovalCallback(CountDownLatch latch) {
         return new FaceManager.RemovalCallback() {
             @Override
-            public void onRemovalError(Face face, int errMsgId, CharSequence err) {
-                Slog.e(TAG, String.format("Can't remove face %d. Reason: %s",
-                        face.getBiometricId(), err));
+            public void onRemovalError(@Nullable Face face, int errMsgId, CharSequence err) {
+                Slog.e(TAG, "Unable to remove face, error: " + err);
                 latch.countDown();
             }
 
diff --git a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
index 4f4b4f2..639dda6 100644
--- a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
+++ b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
@@ -121,8 +121,8 @@
             StatsEvent statsEvent = StatsEvent.newBuilder()
                     .setAtomId(321)
                     .writeString(sessionId)
-                    .writeInt(event.getType())
-                    .writeLong(event.getTimeSincePlaybackCreatedMillis())
+                    .writeInt(event.getNetworkType())
+                    .writeLong(event.getTimeSinceCreatedMillis())
                     .usePooledBuffer()
                     .build();
             StatsLog.write(statsEvent);
@@ -132,7 +132,7 @@
         public void reportTrackChangeEvent(
                 String sessionId, TrackChangeEvent event, int userId) {
             StatsEvent statsEvent = StatsEvent.newBuilder()
-                    .setAtomId(321)
+                    .setAtomId(324)
                     .writeString(sessionId)
                     .writeInt(event.getTrackState())
                     .writeInt(event.getTrackChangeReason())
@@ -140,7 +140,7 @@
                     .writeString(event.getSampleMimeType())
                     .writeString(event.getCodecName())
                     .writeInt(event.getBitrate())
-                    .writeLong(event.getTimeSincePlaybackCreatedMillis())
+                    .writeLong(event.getTimeSinceCreatedMillis())
                     .writeInt(event.getTrackType())
                     .writeString(event.getLanguage())
                     .writeString(event.getLanguageRegion())
diff --git a/services/core/java/com/android/server/om/OverlayActorEnforcer.java b/services/core/java/com/android/server/om/OverlayActorEnforcer.java
index 2d540de..c4b6485 100644
--- a/services/core/java/com/android/server/om/OverlayActorEnforcer.java
+++ b/services/core/java/com/android/server/om/OverlayActorEnforcer.java
@@ -23,6 +23,7 @@
 import android.os.Process;
 import android.text.TextUtils;
 import android.util.Pair;
+import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
@@ -42,9 +43,6 @@
  */
 public class OverlayActorEnforcer {
 
-    // By default, the reason is not logged to prevent leaks of why it failed
-    private static final boolean DEBUG_REASON = false;
-
     private final PackageManagerHelper mPackageManager;
 
     /**
@@ -85,17 +83,18 @@
 
     void enforceActor(@NonNull OverlayInfo overlayInfo, @NonNull String methodName,
             int callingUid, int userId) throws SecurityException {
-        ActorState actorState = isAllowedActor(methodName, overlayInfo, callingUid, userId);
+        final ActorState actorState = isAllowedActor(methodName, overlayInfo, callingUid, userId);
         if (actorState == ActorState.ALLOWED) {
             return;
         }
 
-        String targetOverlayableName = overlayInfo.targetOverlayableName;
-        throw new SecurityException("UID" + callingUid + " is not allowed to call "
-                + methodName + " for "
+        final String targetOverlayableName = overlayInfo.targetOverlayableName;
+        final String errorMessage = "UID" + callingUid + " is not allowed to call " + methodName
+                + " for "
                 + (TextUtils.isEmpty(targetOverlayableName) ? "" : (targetOverlayableName + " in "))
-                + overlayInfo.targetPackageName + (DEBUG_REASON ? (" because " + actorState) : "")
-        );
+                + overlayInfo.targetPackageName + " for user " + userId;
+        Slog.w(OverlayManagerService.TAG, errorMessage + " because " + actorState);
+        throw new SecurityException(errorMessage);
     }
 
     /**
diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java
index a83edb7..9984bfa 100644
--- a/services/core/java/com/android/server/os/NativeTombstoneManager.java
+++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java
@@ -16,17 +16,29 @@
 
 package com.android.server.os;
 
+import static android.app.ApplicationExitInfo.REASON_CRASH_NATIVE;
+import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
 import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
 import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
 
 import android.annotation.AppIdInt;
+import android.annotation.CurrentTimeMillisLong;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.app.ActivityManager.RunningAppProcessInfo;
+import android.app.ApplicationExitInfo;
+import android.app.IParcelFileDescriptorRetriever;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.os.FileObserver;
 import android.os.Handler;
 import android.os.ParcelFileDescriptor;
 import android.os.UserHandle;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructStat;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.proto.ProtoInputStream;
@@ -34,6 +46,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.BootReceiver;
 import com.android.server.ServiceThread;
+import com.android.server.os.TombstoneProtos.Cause;
 import com.android.server.os.TombstoneProtos.Tombstone;
 
 import libcore.io.IoUtils;
@@ -42,7 +55,11 @@
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
 
 /**
  * A class to manage native tombstones.
@@ -75,6 +92,9 @@
     }
 
     void onSystemReady() {
+        registerForUserRemoval();
+        registerForPackageRemoval();
+
         // Scan existing tombstones.
         mHandler.post(() -> {
             final File[] tombstoneFiles = TOMBSTONE_DIR.listFiles();
@@ -94,8 +114,9 @@
 
         if (filename.endsWith(".pb")) {
             handleProtoTombstone(path);
+            BootReceiver.addTombstoneToDropBox(mContext, path, true);
         } else {
-            BootReceiver.addTombstoneToDropBox(mContext, path);
+            BootReceiver.addTombstoneToDropBox(mContext, path, false);
         }
     }
 
@@ -145,18 +166,164 @@
         }
     }
 
+    /**
+     * Remove native tombstones matching a user and/or app.
+     *
+     * @param userId user id to filter by, selects all users if empty
+     * @param appId app id to filter by, selects all users if empty
+     */
+    public void purge(Optional<Integer> userId, Optional<Integer> appId) {
+        mHandler.post(() -> {
+            synchronized (mLock) {
+                for (int i = mTombstones.size() - 1; i >= 0; --i) {
+                    TombstoneFile tombstone = mTombstones.valueAt(i);
+                    if (tombstone.matches(userId, appId)) {
+                        tombstone.purge();
+                        mTombstones.removeAt(i);
+                    }
+                }
+            }
+        });
+    }
+
+    private void purgePackage(int uid, boolean allUsers) {
+        final int appId = UserHandle.getAppId(uid);
+        Optional<Integer> userId;
+        if (allUsers) {
+            userId = Optional.empty();
+        } else {
+            userId = Optional.of(UserHandle.getUserId(uid));
+        }
+        purge(userId, Optional.of(appId));
+    }
+
+    private void purgeUser(int uid) {
+        purge(Optional.of(uid), Optional.empty());
+    }
+
+    private void registerForPackageRemoval() {
+        final IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
+        filter.addDataScheme("package");
+        mContext.registerReceiverForAllUsers(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                final int uid = intent.getIntExtra(Intent.EXTRA_UID, UserHandle.USER_NULL);
+                if (uid == UserHandle.USER_NULL) return;
+
+                final boolean allUsers = intent.getBooleanExtra(
+                        Intent.EXTRA_REMOVED_FOR_ALL_USERS, false);
+
+                purgePackage(uid, allUsers);
+            }
+        }, filter, null, mHandler);
+    }
+
+    private void registerForUserRemoval() {
+        final IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_USER_REMOVED);
+        mContext.registerReceiverForAllUsers(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+                if (userId < 1) return;
+
+                purgeUser(userId);
+            }
+        }, filter, null, mHandler);
+    }
+
+    /**
+     * Collect native tombstones.
+     *
+     * @param output list to append to
+     * @param callingUid POSIX uid to filter by
+     * @param pid pid to filter by, ignored if zero
+     * @param maxNum maximum number of elements in output
+     */
+    public void collectTombstones(ArrayList<ApplicationExitInfo> output, int callingUid, int pid,
+            int maxNum) {
+        CompletableFuture<Object> future = new CompletableFuture<>();
+
+        if (!UserHandle.isApp(callingUid)) {
+            return;
+        }
+
+        final int userId = UserHandle.getUserId(callingUid);
+        final int appId = UserHandle.getAppId(callingUid);
+
+        mHandler.post(() -> {
+            boolean appendedTombstones = false;
+
+            synchronized (mLock) {
+                final int tombstonesSize = mTombstones.size();
+
+            tombstoneIter:
+                for (int i = 0; i < tombstonesSize; ++i) {
+                    TombstoneFile tombstone = mTombstones.valueAt(i);
+                    if (tombstone.matches(Optional.of(userId), Optional.of(appId))) {
+                        if (pid != 0 && tombstone.mPid != pid) {
+                            continue;
+                        }
+
+                        // Try to attach to an existing REASON_CRASH_NATIVE.
+                        final int outputSize = output.size();
+                        for (int j = 0; j < outputSize; ++j) {
+                            ApplicationExitInfo exitInfo = output.get(j);
+                            if (tombstone.matches(exitInfo)) {
+                                exitInfo.setNativeTombstoneRetriever(tombstone.getPfdRetriever());
+                                continue tombstoneIter;
+                            }
+                        }
+
+                        if (output.size() < maxNum) {
+                            appendedTombstones = true;
+                            output.add(tombstone.toAppExitInfo());
+                        }
+                    }
+                }
+            }
+
+            if (appendedTombstones) {
+                Collections.sort(output, (lhs, rhs) -> {
+                    // Reports should be ordered with newest reports first.
+                    long diff = rhs.getTimestamp() - lhs.getTimestamp();
+                    if (diff < 0) {
+                        return -1;
+                    } else if (diff == 0) {
+                        return 0;
+                    } else {
+                        return 1;
+                    }
+                });
+            }
+            future.complete(null);
+        });
+
+        try {
+            future.get();
+        } catch (ExecutionException | InterruptedException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
     static class TombstoneFile {
         final ParcelFileDescriptor mPfd;
 
-        final @UserIdInt int mUserId;
-        final @AppIdInt int mAppId;
+        @UserIdInt int mUserId;
+        @AppIdInt int mAppId;
+
+        int mPid;
+        int mUid;
+        String mProcessName;
+        @CurrentTimeMillisLong long mTimestampMs;
+        String mCrashReason;
 
         boolean mPurged = false;
+        final IParcelFileDescriptorRetriever mRetriever = new ParcelFileDescriptorRetriever();
 
-        TombstoneFile(ParcelFileDescriptor pfd, @UserIdInt int userId, @AppIdInt int appId) {
+        TombstoneFile(ParcelFileDescriptor pfd) {
             mPfd = pfd;
-            mUserId = userId;
-            mAppId = appId;
         }
 
         public boolean matches(Optional<Integer> userId, Optional<Integer> appId) {
@@ -175,24 +342,90 @@
             return true;
         }
 
+        public boolean matches(ApplicationExitInfo exitInfo) {
+            if (exitInfo.getReason() != REASON_CRASH_NATIVE) {
+                return false;
+            }
+
+            if (exitInfo.getPid() != mPid) {
+                return false;
+            }
+
+            if (exitInfo.getRealUid() != mUid) {
+                return false;
+            }
+
+            if (Math.abs(exitInfo.getTimestamp() - mTimestampMs) > 1000) {
+                return false;
+            }
+
+            return true;
+        }
+
         public void dispose() {
             IoUtils.closeQuietly(mPfd);
         }
 
+        public void purge() {
+            if (!mPurged) {
+                // There's no way to atomically unlink a specific file for which we have an fd from
+                // a path, which means that we can't safely delete a tombstone without coordination
+                // with tombstoned (which has a risk of deadlock if for example, system_server hangs
+                // with a flock). Do the next best thing, and just truncate the file.
+                //
+                // We don't have to worry about inflicting a SIGBUS on a process that has the
+                // tombstone mmaped, because we only clear if the package has been removed, which
+                // means no one with access to the tombstone should be left.
+                try {
+                    Os.ftruncate(mPfd.getFileDescriptor(), 0);
+                } catch (ErrnoException ex) {
+                    Slog.e(TAG, "Failed to truncate tombstone", ex);
+                }
+                mPurged = true;
+            }
+        }
+
         static Optional<TombstoneFile> parse(ParcelFileDescriptor pfd) {
             final FileInputStream is = new FileInputStream(pfd.getFileDescriptor());
             final ProtoInputStream stream = new ProtoInputStream(is);
 
+            int pid = 0;
             int uid = 0;
+            String processName = "";
+            String crashReason = "";
             String selinuxLabel = "";
 
             try {
                 while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
                     switch (stream.getFieldNumber()) {
+                        case (int) Tombstone.PID:
+                            pid = stream.readInt(Tombstone.PID);
+                            break;
+
                         case (int) Tombstone.UID:
                             uid = stream.readInt(Tombstone.UID);
                             break;
 
+                        case (int) Tombstone.PROCESS_NAME:
+                            processName = stream.readString(Tombstone.PROCESS_NAME);
+                            break;
+
+                        case (int) Tombstone.CAUSE:
+                            long token = stream.start(Tombstone.CAUSE);
+                        cause:
+                            while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                                switch (stream.getFieldNumber()) {
+                                    case (int) Cause.HUMAN_READABLE:
+                                        crashReason = stream.readString(Cause.HUMAN_READABLE);
+                                        break cause;
+
+                                    default:
+                                        break;
+                                }
+                            }
+                            stream.end(token);
+
+
                         case (int) Tombstone.SELINUX_LABEL:
                             selinuxLabel = stream.readString(Tombstone.SELINUX_LABEL);
                             break;
@@ -211,6 +444,14 @@
                 return Optional.empty();
             }
 
+            long timestampMs = 0;
+            try {
+                StructStat stat = Os.fstat(pfd.getFileDescriptor());
+                timestampMs = stat.st_atim.tv_sec * 1000 + stat.st_atim.tv_nsec / 1000000;
+            } catch (ErrnoException ex) {
+                Slog.e(TAG, "Failed to get timestamp of tombstone", ex);
+            }
+
             final int userId = UserHandle.getUserId(uid);
             final int appId = UserHandle.getAppId(uid);
 
@@ -219,7 +460,74 @@
                 return Optional.empty();
             }
 
-            return Optional.of(new TombstoneFile(pfd, userId, appId));
+            TombstoneFile result = new TombstoneFile(pfd);
+
+            result.mUserId = userId;
+            result.mAppId = appId;
+            result.mPid = pid;
+            result.mUid = uid;
+            result.mProcessName = processName;
+            result.mTimestampMs = timestampMs;
+            result.mCrashReason = crashReason;
+
+            return Optional.of(result);
+        }
+
+        public IParcelFileDescriptorRetriever getPfdRetriever() {
+            return mRetriever;
+        }
+
+        public ApplicationExitInfo toAppExitInfo() {
+            ApplicationExitInfo info = new ApplicationExitInfo();
+            info.setPid(mPid);
+            info.setRealUid(mUid);
+            info.setPackageUid(mUid);
+            info.setDefiningUid(mUid);
+            info.setProcessName(mProcessName);
+            info.setReason(ApplicationExitInfo.REASON_CRASH_NATIVE);
+
+            // Signal numbers are architecture-specific!
+            // We choose to provide nothing here, to avoid leading users astray.
+            info.setStatus(0);
+
+            // No way for us to find out.
+            info.setImportance(RunningAppProcessInfo.IMPORTANCE_GONE);
+            info.setPackageName("");
+            info.setProcessStateSummary(null);
+
+            // We could find out, but they didn't get OOM-killed...
+            info.setPss(0);
+            info.setRss(0);
+
+            info.setTimestamp(mTimestampMs);
+            info.setDescription(mCrashReason);
+
+            info.setSubReason(ApplicationExitInfo.SUBREASON_UNKNOWN);
+            info.setNativeTombstoneRetriever(mRetriever);
+
+            return info;
+        }
+
+
+        class ParcelFileDescriptorRetriever extends IParcelFileDescriptorRetriever.Stub {
+            ParcelFileDescriptorRetriever() {}
+
+            public @Nullable ParcelFileDescriptor getPfd() {
+                if (mPurged) {
+                    return null;
+                }
+
+                // Reopen the file descriptor as read-only.
+                try {
+                    final String path = "/proc/self/fd/" + mPfd.getFd();
+                    ParcelFileDescriptor pfd = ParcelFileDescriptor.open(new File(path),
+                            MODE_READ_ONLY);
+                    return pfd;
+                } catch (FileNotFoundException ex) {
+                    Slog.e(TAG, "failed to reopen file descriptor as read-only", ex);
+                    return null;
+                }
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 0ce2673..5cb9d8f 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -70,6 +70,7 @@
 import android.content.pm.IPackageInstallObserver2;
 import android.content.pm.IPackageInstallerSession;
 import android.content.pm.IPackageInstallerSessionFileSystemConnector;
+import android.content.pm.IPackageLoadingProgressCallback;
 import android.content.pm.InstallationFile;
 import android.content.pm.InstallationFileParcel;
 import android.content.pm.PackageInfo;
@@ -321,6 +322,8 @@
     private float mProgress = 0;
     @GuardedBy("mLock")
     private float mReportedProgress = -1;
+    @GuardedBy("mLock")
+    private float mIncrementalProgress = 0;
 
     /** State of the session. */
     @GuardedBy("mLock")
@@ -3770,7 +3773,15 @@
 
                 mIncrementalFileStorages = IncrementalFileStorages.initialize(mContext, stageDir,
                         inheritedDir, params, statusListener, healthCheckParams, healthListener,
-                        addedFiles, perUidReadTimeouts);
+                        addedFiles, perUidReadTimeouts,
+                        new IPackageLoadingProgressCallback.Stub() {
+                            @Override
+                            public void onPackageLoadingProgressChanged(float progress) {
+                                synchronized (mLock) {
+                                    mIncrementalProgress = progress;
+                                }
+                            }
+                        });
                 return false;
             } catch (IOException e) {
                 throw new PackageManagerException(INSTALL_FAILED_MEDIA_UNAVAILABLE, e.getMessage(),
diff --git a/services/core/java/com/android/server/pm/SettingsXml.java b/services/core/java/com/android/server/pm/SettingsXml.java
index 9588a27..ec643f5 100644
--- a/services/core/java/com/android/server/pm/SettingsXml.java
+++ b/services/core/java/com/android/server/pm/SettingsXml.java
@@ -181,7 +181,10 @@
         }
 
         private void moveToFirstTag() throws IOException, XmlPullParserException {
-            // Move to first tag
+            if (mParser.getEventType() == XmlPullParser.START_TAG) {
+                return;
+            }
+
             int type;
             //noinspection StatementWithEmptyBody
             while ((type = mParser.next()) != XmlPullParser.START_TAG
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 753f22d..24c27be 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1522,6 +1522,14 @@
     }
 
     @Override
+    public boolean isUserForeground() {
+        int callingUserId = Binder.getCallingUserHandle().getIdentifier();
+        int currentUser = Binder.withCleanCallingIdentity(() -> ActivityManager.getCurrentUser());
+        // TODO(b/179163496): should return true for profile users of the current user as well
+        return currentUser == callingUserId;
+    }
+
+    @Override
     public String getUserName() {
         if (!hasManageUsersOrPermission(android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED)) {
             throw new SecurityException("You need MANAGE_USERS or GET_ACCOUNTS_PRIVILEGED "
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
index 86a92d79..e5ed774 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -1210,7 +1210,8 @@
 
         // Filter to highest, non-zero packages
         ArraySet<String> approvedPackages = new ArraySet<>();
-        for (int index = 0; index < infosSize; index++) {
+        int approvalsSize = packageApprovals.size();
+        for (int index = 0; index < approvalsSize; index++) {
             if (packageApprovals.valueAt(index) == highestApproval) {
                 approvedPackages.add(packageApprovals.keyAt(index));
             }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 6a441f1..4ccd57d 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -56,13 +56,11 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
-import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
 import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
 import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION;
 import static android.view.WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION;
 import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG;
-import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
@@ -2292,25 +2290,6 @@
         return attrs.type == TYPE_NOTIFICATION_SHADE;
     }
 
-    @Override
-    public boolean canBeHiddenByKeyguardLw(WindowState win) {
-
-        // Keyguard visibility of window from activities are determined over activity visibility.
-        if (win.getAppToken() != null) {
-            return false;
-        }
-        switch (win.getAttrs().type) {
-            case TYPE_NOTIFICATION_SHADE:
-            case TYPE_STATUS_BAR:
-            case TYPE_NAVIGATION_BAR:
-            case TYPE_WALLPAPER:
-                return false;
-            default:
-                // Hide only windows below the keyguard host window.
-                return getWindowLayerLw(win) < getWindowLayerFromTypeLw(TYPE_NOTIFICATION_SHADE);
-        }
-    }
-
     /** {@inheritDoc} */
     @Override
     public StartingSurface addSplashScreen(IBinder appToken, String packageName, int theme,
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index db33e75..b5a9aca 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -672,7 +672,22 @@
     /**
      * @return whether {@param win} can be hidden by Keyguard
      */
-    public boolean canBeHiddenByKeyguardLw(WindowState win);
+    default boolean canBeHiddenByKeyguardLw(WindowState win) {
+        // Keyguard visibility of window from activities are determined over activity visibility.
+        if (win.getAppToken() != null) {
+            return false;
+        }
+        switch (win.getAttrs().type) {
+            case TYPE_NOTIFICATION_SHADE:
+            case TYPE_STATUS_BAR:
+            case TYPE_NAVIGATION_BAR:
+            case TYPE_WALLPAPER:
+                return false;
+            default:
+                // Hide only windows below the keyguard host window.
+                return getWindowLayerLw(win) < getWindowLayerFromTypeLw(TYPE_NOTIFICATION_SHADE);
+        }
+    }
 
     /**
      * Called when the system would like to show a UI to indicate that an
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java b/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
index 6d9cb75..2a95416 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
@@ -47,7 +47,8 @@
     private static final long DELETE_AGE_MILLIS = 48 * MILLISECONDS_PER_HOUR;
 
     private final ReentrantLock mLock = new ReentrantLock();
-    private File mDataStorageDir;
+    private final File mDataStorageDir;
+    private final String mDataStorageFilename;
     private final FileRotator mFileRotator;
 
     private static class DataElement {
@@ -168,6 +169,7 @@
     public PowerStatsDataStorage(Context context, File dataStoragePath,
             String dataStorageFilename) {
         mDataStorageDir = dataStoragePath;
+        mDataStorageFilename = dataStorageFilename;
 
         if (!mDataStorageDir.exists() && !mDataStorageDir.mkdirs()) {
             Slog.wtf(TAG, "mDataStorageDir does not exist: " + mDataStorageDir.getPath());
@@ -177,33 +179,35 @@
             // filename, so any files that don't match the current version number can be deleted.
             File[] files = mDataStorageDir.listFiles();
             for (int i = 0; i < files.length; i++) {
-                // Meter and model files are stored in the same directory.
+                // Meter, model, and residency files are stored in the same directory.
                 //
                 // The format of filenames on disk is:
                 //    log.powerstats.meter.version.timestamp
                 //    log.powerstats.model.version.timestamp
+                //    log.powerstats.residency.version.timestamp
                 //
                 // The format of dataStorageFilenames is:
                 //    log.powerstats.meter.version
                 //    log.powerstats.model.version
+                //    log.powerstats.residency.version
                 //
-                // A PowerStatsDataStorage object is created for meter and model data.  Strip off
-                // the version and check that the current file we're checking starts with the stem
-                // (log.powerstats.meter or log.powerstats.model). If the stem matches and the
-                // version number is different, delete the old file.
-                int versionDot = dataStorageFilename.lastIndexOf('.');
-                String beforeVersionDot = dataStorageFilename.substring(0, versionDot);
+                // A PowerStatsDataStorage object is created for meter, model, and residency data.
+                // Strip off the version and check that the current file we're checking starts with
+                // the stem (log.powerstats.meter, log.powerstats.model, log.powerstats.residency).
+                // If the stem matches and the version number is different, delete the old file.
+                int versionDot = mDataStorageFilename.lastIndexOf('.');
+                String beforeVersionDot = mDataStorageFilename.substring(0, versionDot);
                 // Check that the stems match.
                 if (files[i].getName().startsWith(beforeVersionDot)) {
                     // Check that the version number matches.  If not, delete the old file.
-                    if (!files[i].getName().startsWith(dataStorageFilename)) {
+                    if (!files[i].getName().startsWith(mDataStorageFilename)) {
                         files[i].delete();
                     }
                 }
             }
 
             mFileRotator = new FileRotator(mDataStorageDir,
-                                           dataStorageFilename,
+                                           mDataStorageFilename,
                                            ROTATE_AGE_MILLIS,
                                            DELETE_AGE_MILLIS);
         }
@@ -242,4 +246,19 @@
     public void read(DataElementReadCallback callback) throws IOException {
         mFileRotator.readMatching(new DataReader(callback), Long.MIN_VALUE, Long.MAX_VALUE);
     }
+
+    /**
+     * Deletes all stored log data.
+     */
+    public void deleteLogs() {
+        File[] files = mDataStorageDir.listFiles();
+        for (int i = 0; i < files.length; i++) {
+            int versionDot = mDataStorageFilename.lastIndexOf('.');
+            String beforeVersionDot = mDataStorageFilename.substring(0, versionDot);
+            // Check that the stems before the version match.
+            if (files[i].getName().startsWith(beforeVersionDot)) {
+                files[i].delete();
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
index 37fc5a0..c4f29ea 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
@@ -26,6 +26,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.proto.ProtoInputStream;
 import android.util.proto.ProtoOutputStream;
@@ -41,14 +42,17 @@
 import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.io.IOException;
+import java.util.Arrays;
 
 /**
- * PowerStatsLogger is responsible for logging model and meter energy data to on-device storage.
- * Messages are sent to its message handler to request that energy data be logged, at which time it
- * queries the PowerStats HAL and logs the data to on-device storage.  The on-device storage is
- * dumped to file by calling writeModelDataToFile, writeMeterDataToFile, or writeResidencyDataToFile
- * with a file descriptor that points to the output file.
+ * PowerStatsLogger is responsible for logging model, meter, and residency data to on-device
+ * storage.  Messages are sent to its message handler to request that energy data be logged, at
+ * which time it queries the PowerStats HAL and logs the data to on-device storage.  The on-device
+ * storage is dumped to file by calling writeModelDataToFile, writeMeterDataToFile, or
+ * writeResidencyDataToFile with a file descriptor that points to the output file.
  */
 public final class PowerStatsLogger extends Handler {
     private static final String TAG = PowerStatsLogger.class.getSimpleName();
@@ -61,6 +65,10 @@
     private final PowerStatsDataStorage mPowerStatsModelStorage;
     private final PowerStatsDataStorage mPowerStatsResidencyStorage;
     private final IPowerStatsHALWrapper mPowerStatsHALWrapper;
+    private File mDataStoragePath;
+    private boolean mDeleteMeterDataOnBoot;
+    private boolean mDeleteModelDataOnBoot;
+    private boolean mDeleteResidencyDataOnBoot;
 
     @Override
     public void handleMessage(Message msg) {
@@ -230,16 +238,99 @@
         pos.flush();
     }
 
-    public PowerStatsLogger(Context context, File dataStoragePath, String meterFilename,
-            String modelFilename, String residencyFilename,
+    private boolean dataChanged(String cachedFilename, byte[] dataCurrent) {
+        boolean dataChanged = false;
+
+        if (mDataStoragePath.exists() || mDataStoragePath.mkdirs()) {
+            final File cachedFile = new File(mDataStoragePath, cachedFilename);
+
+            if (cachedFile.exists()) {
+                // Get the byte array for the cached data.
+                final byte[] dataCached = new byte[(int) cachedFile.length()];
+
+                // Get the cached data from file.
+                try {
+                    final FileInputStream fis = new FileInputStream(cachedFile.getPath());
+                    fis.read(dataCached);
+                } catch (IOException e) {
+                    Slog.e(TAG, "Failed to read cached data from file");
+                }
+
+                // If the cached and current data are different, delete the data store.
+                dataChanged = !Arrays.equals(dataCached, dataCurrent);
+            } else {
+                // Either the cached file was somehow deleted, or this is the first
+                // boot of the device and we're creating the file for the first time.
+                // In either case, delete the log files.
+                dataChanged = true;
+            }
+        }
+
+        return dataChanged;
+    }
+
+    private void updateCacheFile(String cacheFilename, byte[] data) {
+        try {
+            final AtomicFile atomicCachedFile =
+                    new AtomicFile(new File(mDataStoragePath, cacheFilename));
+            final FileOutputStream fos = atomicCachedFile.startWrite();
+            fos.write(data);
+            atomicCachedFile.finishWrite(fos);
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to write current data to cached file");
+        }
+    }
+
+    public boolean getDeleteMeterDataOnBoot() {
+        return mDeleteMeterDataOnBoot;
+    }
+
+    public boolean getDeleteModelDataOnBoot() {
+        return mDeleteModelDataOnBoot;
+    }
+
+    public boolean getDeleteResidencyDataOnBoot() {
+        return mDeleteResidencyDataOnBoot;
+    }
+
+    public PowerStatsLogger(Context context, File dataStoragePath,
+            String meterFilename, String meterCacheFilename,
+            String modelFilename, String modelCacheFilename,
+            String residencyFilename, String residencyCacheFilename,
             IPowerStatsHALWrapper powerStatsHALWrapper) {
         super(Looper.getMainLooper());
         mPowerStatsHALWrapper = powerStatsHALWrapper;
-        mPowerStatsMeterStorage = new PowerStatsDataStorage(context, dataStoragePath,
+        mDataStoragePath = dataStoragePath;
+
+        mPowerStatsMeterStorage = new PowerStatsDataStorage(context, mDataStoragePath,
             meterFilename);
-        mPowerStatsModelStorage = new PowerStatsDataStorage(context, dataStoragePath,
+        mPowerStatsModelStorage = new PowerStatsDataStorage(context, mDataStoragePath,
             modelFilename);
-        mPowerStatsResidencyStorage = new PowerStatsDataStorage(context, dataStoragePath,
+        mPowerStatsResidencyStorage = new PowerStatsDataStorage(context, mDataStoragePath,
             residencyFilename);
+
+        final Channel[] channels = mPowerStatsHALWrapper.getEnergyMeterInfo();
+        final byte[] channelBytes = ChannelUtils.getProtoBytes(channels);
+        mDeleteMeterDataOnBoot = dataChanged(meterCacheFilename, channelBytes);
+        if (mDeleteMeterDataOnBoot) {
+            mPowerStatsMeterStorage.deleteLogs();
+            updateCacheFile(meterCacheFilename, channelBytes);
+        }
+
+        final EnergyConsumer[] energyConsumers = mPowerStatsHALWrapper.getEnergyConsumerInfo();
+        final byte[] energyConsumerBytes = EnergyConsumerUtils.getProtoBytes(energyConsumers);
+        mDeleteModelDataOnBoot = dataChanged(modelCacheFilename, energyConsumerBytes);
+        if (mDeleteModelDataOnBoot) {
+            mPowerStatsModelStorage.deleteLogs();
+            updateCacheFile(modelCacheFilename, energyConsumerBytes);
+        }
+
+        final PowerEntity[] powerEntities = mPowerStatsHALWrapper.getPowerEntityInfo();
+        final byte[] powerEntityBytes = PowerEntityUtils.getProtoBytes(powerEntities);
+        mDeleteResidencyDataOnBoot = dataChanged(residencyCacheFilename, powerEntityBytes);
+        if (mDeleteResidencyDataOnBoot) {
+            mPowerStatsResidencyStorage.deleteLogs();
+            updateCacheFile(residencyCacheFilename, powerEntityBytes);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java
index b7285d5..bb52c1d 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsService.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java
@@ -61,8 +61,12 @@
     private static final String MODEL_FILENAME = "log.powerstats.model." + DATA_STORAGE_VERSION;
     private static final String RESIDENCY_FILENAME =
             "log.powerstats.residency." + DATA_STORAGE_VERSION;
+    private static final String METER_CACHE_FILENAME = "meterCache";
+    private static final String MODEL_CACHE_FILENAME = "modelCache";
+    private static final String RESIDENCY_CACHE_FILENAME = "residencyCache";
 
     private final Injector mInjector;
+    private File mDataStoragePath;
 
     private Context mContext;
     @Nullable
@@ -98,6 +102,18 @@
             return RESIDENCY_FILENAME;
         }
 
+        String createMeterCacheFilename() {
+            return METER_CACHE_FILENAME;
+        }
+
+        String createModelCacheFilename() {
+            return MODEL_CACHE_FILENAME;
+        }
+
+        String createResidencyCacheFilename() {
+            return RESIDENCY_CACHE_FILENAME;
+        }
+
         IPowerStatsHALWrapper createPowerStatsHALWrapperImpl() {
             return PowerStatsHALWrapper.getPowerStatsHalImpl();
         }
@@ -112,10 +128,15 @@
         }
 
         PowerStatsLogger createPowerStatsLogger(Context context, File dataStoragePath,
-                String meterFilename, String modelFilename, String residencyFilename,
+                String meterFilename, String meterCacheFilename,
+                String modelFilename, String modelCacheFilename,
+                String residencyFilename, String residencyCacheFilename,
                 IPowerStatsHALWrapper powerStatsHALWrapper) {
-            return new PowerStatsLogger(context, dataStoragePath, meterFilename,
-                modelFilename, residencyFilename, powerStatsHALWrapper);
+            return new PowerStatsLogger(context, dataStoragePath,
+                meterFilename, meterCacheFilename,
+                modelFilename, modelCacheFilename,
+                residencyFilename, residencyCacheFilename,
+                powerStatsHALWrapper);
         }
 
         BatteryTrigger createBatteryTrigger(Context context, PowerStatsLogger powerStatsLogger) {
@@ -187,14 +208,31 @@
         mPullAtomCallback = mInjector.createStatsPullerImpl(mContext, mPowerStatsInternal);
     }
 
+    @VisibleForTesting
+    public boolean getDeleteMeterDataOnBoot() {
+        return mPowerStatsLogger.getDeleteMeterDataOnBoot();
+    }
+
+    @VisibleForTesting
+    public boolean getDeleteModelDataOnBoot() {
+        return mPowerStatsLogger.getDeleteModelDataOnBoot();
+    }
+
+    @VisibleForTesting
+    public boolean getDeleteResidencyDataOnBoot() {
+        return mPowerStatsLogger.getDeleteResidencyDataOnBoot();
+    }
+
     private void onBootCompleted() {
         if (getPowerStatsHal().isInitialized()) {
             if (DEBUG) Slog.d(TAG, "Starting PowerStatsService loggers");
+            mDataStoragePath = mInjector.createDataStoragePath();
 
             // Only start logger and triggers if initialization is successful.
-            mPowerStatsLogger = mInjector.createPowerStatsLogger(mContext,
-                mInjector.createDataStoragePath(), mInjector.createMeterFilename(),
-                mInjector.createModelFilename(), mInjector.createResidencyFilename(),
+            mPowerStatsLogger = mInjector.createPowerStatsLogger(mContext, mDataStoragePath,
+                mInjector.createMeterFilename(), mInjector.createMeterCacheFilename(),
+                mInjector.createModelFilename(), mInjector.createModelCacheFilename(),
+                mInjector.createResidencyFilename(), mInjector.createResidencyCacheFilename(),
                 getPowerStatsHal());
             mBatteryTrigger = mInjector.createBatteryTrigger(mContext, mPowerStatsLogger);
             mTimerTrigger = mInjector.createTimerTrigger(mContext, mPowerStatsLogger);
diff --git a/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java b/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java
index bd003d3..11b22a5 100644
--- a/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java
+++ b/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java
@@ -49,6 +49,12 @@
     private static final String TAG = ProtoStreamUtils.class.getSimpleName();
 
     static class PowerEntityUtils {
+        public static byte[] getProtoBytes(PowerEntity[] powerEntity) {
+            ProtoOutputStream pos = new ProtoOutputStream();
+            packProtoMessage(powerEntity, pos);
+            return pos.getBytes();
+        }
+
         public static void packProtoMessage(PowerEntity[] powerEntity,
                 ProtoOutputStream pos) {
             if (powerEntity == null) return;
@@ -260,6 +266,12 @@
     }
 
     static class ChannelUtils {
+        public static byte[] getProtoBytes(Channel[] channel) {
+            ProtoOutputStream pos = new ProtoOutputStream();
+            packProtoMessage(channel, pos);
+            return pos.getBytes();
+        }
+
         public static void packProtoMessage(Channel[] channel, ProtoOutputStream pos) {
             if (channel == null) return;
 
@@ -396,6 +408,12 @@
     }
 
     static class EnergyConsumerUtils {
+        public static byte[] getProtoBytes(EnergyConsumer[] energyConsumer) {
+            ProtoOutputStream pos = new ProtoOutputStream();
+            packProtoMessage(energyConsumer, pos);
+            return pos.getBytes();
+        }
+
         public static void packProtoMessage(EnergyConsumer[] energyConsumer,
                 ProtoOutputStream pos) {
             if (energyConsumer == null) return;
@@ -410,6 +428,72 @@
             }
         }
 
+        public static EnergyConsumer[] unpackProtoMessage(byte[] data) throws IOException {
+            final ProtoInputStream pis = new ProtoInputStream(new ByteArrayInputStream(data));
+            List<EnergyConsumer> energyConsumerList = new ArrayList<EnergyConsumer>();
+
+            while (true) {
+                try {
+                    int nextField = pis.nextField();
+                    EnergyConsumer energyConsumer = new EnergyConsumer();
+
+                    if (nextField == (int) PowerStatsServiceModelProto.ENERGY_CONSUMER) {
+                        long token = pis.start(PowerStatsServiceModelProto.ENERGY_CONSUMER);
+                        energyConsumerList.add(unpackEnergyConsumerProto(pis));
+                        pis.end(token);
+                    } else if (nextField == ProtoInputStream.NO_MORE_FIELDS) {
+                        return energyConsumerList.toArray(
+                            new EnergyConsumer[energyConsumerList.size()]);
+                    } else {
+                        Slog.e(TAG, "Unhandled field in proto: "
+                                + ProtoUtils.currentFieldToString(pis));
+                    }
+                } catch (WireTypeMismatchException wtme) {
+                    Slog.e(TAG, "Wire Type mismatch in proto: "
+                            + ProtoUtils.currentFieldToString(pis));
+                }
+            }
+        }
+
+        private static EnergyConsumer unpackEnergyConsumerProto(ProtoInputStream pis)
+                throws IOException {
+            final EnergyConsumer energyConsumer = new EnergyConsumer();
+
+            while (true) {
+                try {
+                    switch (pis.nextField()) {
+                        case (int) EnergyConsumerProto.ID:
+                            energyConsumer.id = pis.readInt(EnergyConsumerProto.ID);
+                            break;
+
+                        case (int) EnergyConsumerProto.ORDINAL:
+                            energyConsumer.ordinal = pis.readInt(EnergyConsumerProto.ORDINAL);
+                            break;
+
+                        case (int) EnergyConsumerProto.TYPE:
+                            energyConsumer.type = (byte) pis.readInt(EnergyConsumerProto.TYPE);
+                            break;
+
+                        case (int) EnergyConsumerProto.NAME:
+                            energyConsumer.name = pis.readString(EnergyConsumerProto.NAME);
+                            break;
+
+                        case ProtoInputStream.NO_MORE_FIELDS:
+                            return energyConsumer;
+
+                        default:
+                            Slog.e(TAG, "Unhandled field in EnergyConsumerProto: "
+                                    + ProtoUtils.currentFieldToString(pis));
+                            break;
+
+                    }
+                } catch (WireTypeMismatchException wtme) {
+                    Slog.e(TAG, "Wire Type mismatch in EnergyConsumerProto: "
+                            + ProtoUtils.currentFieldToString(pis));
+                }
+            }
+        }
+
         public static void print(EnergyConsumer[] energyConsumer) {
             if (energyConsumer == null) return;
 
diff --git a/services/core/java/com/android/server/security/FileIntegrityService.java b/services/core/java/com/android/server/security/FileIntegrityService.java
index 6ec71b7..74bb993 100644
--- a/services/core/java/com/android/server/security/FileIntegrityService.java
+++ b/services/core/java/com/android/server/security/FileIntegrityService.java
@@ -23,10 +23,8 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.os.Binder;
-import android.os.Build;
 import android.os.Environment;
 import android.os.IBinder;
-import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.security.IFileIntegrityService;
 import android.util.Slog;
@@ -60,7 +58,7 @@
     private final IBinder mService = new IFileIntegrityService.Stub() {
         @Override
         public boolean isApkVeritySupported() {
-            return FileIntegrityService.isApkVeritySupported();
+            return VerityUtils.isFsVeritySupported();
         }
 
         @Override
@@ -69,7 +67,7 @@
             checkCallerPermission(packageName);
 
             try {
-                if (!isApkVeritySupported()) {
+                if (!VerityUtils.isFsVeritySupported()) {
                     return false;
                 }
                 if (certificateBytes == null) {
@@ -110,11 +108,6 @@
         }
     };
 
-    public static boolean isApkVeritySupported() {
-        return Build.VERSION.FIRST_SDK_INT >= Build.VERSION_CODES.R
-                || SystemProperties.getInt("ro.apk_verity.mode", 0) == 2;
-    }
-
     public FileIntegrityService(final Context context) {
         super(context);
         try {
diff --git a/services/core/java/com/android/server/security/VerityUtils.java b/services/core/java/com/android/server/security/VerityUtils.java
index 09ee001..48a60387 100644
--- a/services/core/java/com/android/server/security/VerityUtils.java
+++ b/services/core/java/com/android/server/security/VerityUtils.java
@@ -17,7 +17,9 @@
 package com.android.server.security;
 
 import android.annotation.NonNull;
+import android.os.Build;
 import android.os.SharedMemory;
+import android.os.SystemProperties;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
@@ -57,6 +59,11 @@
 
     private static final boolean DEBUG = false;
 
+    public static boolean isFsVeritySupported() {
+        return Build.VERSION.FIRST_SDK_INT >= Build.VERSION_CODES.R
+                || SystemProperties.getInt("ro.apk_verity.mode", 0) == 2;
+    }
+
     /** Returns true if the given file looks like containing an fs-verity signature. */
     public static boolean isFsveritySignatureFile(File file) {
         return file.getName().endsWith(FSVERITY_SIGNATURE_FILE_EXTENSION);
diff --git a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
index 96248c3..0974537 100644
--- a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
+++ b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
@@ -18,22 +18,65 @@
 
 import static com.android.internal.infra.AbstractRemoteService.PERMANENT_BOUND_TIMEOUT_MS;
 
+import android.annotation.Nullable;
+import android.app.AppOpsManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.os.Binder;
+import android.os.Bundle;
 import android.os.RemoteException;
 import android.speech.IRecognitionListener;
 import android.speech.IRecognitionService;
 import android.speech.RecognitionService;
+import android.speech.SpeechRecognizer;
+import android.util.Log;
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.infra.ServiceConnector;
 
 final class RemoteSpeechRecognitionService extends ServiceConnector.Impl<IRecognitionService> {
     private static final String TAG = RemoteSpeechRecognitionService.class.getSimpleName();
-    private static final boolean DEBUG = true;
+    private static final boolean DEBUG = false;
 
-    RemoteSpeechRecognitionService(Context context, ComponentName serviceName, int userId) {
+    private static final String APP_OP_MESSAGE = "Recording audio for speech recognition";
+    private static final String RECORD_AUDIO_APP_OP =
+            AppOpsManager.permissionToOp(android.Manifest.permission.RECORD_AUDIO);
+
+    private final Object mLock = new Object();
+
+    private boolean mConnected = false;
+
+    @Nullable
+    private IRecognitionListener mListener;
+
+    @Nullable
+    @GuardedBy("mLock")
+    private String mPackageName;
+
+    @Nullable
+    @GuardedBy("mLock")
+    private String mFeatureId;
+
+    @Nullable
+    @GuardedBy("mLock")
+    private DelegatingListener mDelegatingListener;
+
+    // Makes sure we can block startListening() if session is still in progress.
+    @GuardedBy("mLock")
+    private boolean mSessionInProgress = false;
+
+    // Makes sure we call startProxyOp / finishProxyOp at right times and only once per session.
+    @GuardedBy("mLock")
+    private boolean mRecordingInProgress = false;
+
+    private final int mCallingUid;
+    private final AppOpsManager mAppOpsManager;
+    private final ComponentName mComponentName;
+
+    RemoteSpeechRecognitionService(
+            Context context, ComponentName serviceName, int userId, int callingUid) {
         super(context,
                 new Intent(RecognitionService.SERVICE_INTERFACE).setComponent(serviceName),
                 Context.BIND_AUTO_CREATE
@@ -43,46 +86,197 @@
                 userId,
                 IRecognitionService.Stub::asInterface);
 
+        mCallingUid = callingUid;
+        mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
+        mComponentName = serviceName;
+
         if (DEBUG) {
             Slog.i(TAG, "Bound to recognition service at: " + serviceName.flattenToString());
         }
     }
 
-    void startListening(Intent recognizerIntent, IRecognitionListener listener, String packageName,
-            String featureId) throws RemoteException {
-        if (DEBUG) {
-            Slog.i(TAG, "#startListening for package: " + packageName + ", feature=" + featureId);
-        }
-        run(service -> service.startListening(recognizerIntent, listener, packageName, featureId));
+    ComponentName getServiceComponentName() {
+        return mComponentName;
     }
 
-    void stopListening(IRecognitionListener listener, String packageName, String featureId)
-            throws RemoteException {
+    void startListening(Intent recognizerIntent, IRecognitionListener listener, String packageName,
+            String featureId) {
+        if (DEBUG) {
+            Slog.i(TAG, String.format("#startListening for package: %s, feature=%s, callingUid=%d",
+                    packageName, featureId, mCallingUid));
+        }
+
+        if (listener == null) {
+            Log.w(TAG, "#startListening called with no preceding #setListening - ignoring");
+            return;
+        }
+
+        if (!mConnected) {
+            tryRespondWithError(listener, SpeechRecognizer.ERROR_SERVER_DISCONNECTED);
+            return;
+        }
+
+        synchronized (mLock) {
+            if (mSessionInProgress) {
+                Slog.i(TAG, "#startListening called while listening is in progress.");
+                tryRespondWithError(listener, SpeechRecognizer.ERROR_RECOGNIZER_BUSY);
+                return;
+            }
+
+            if (startProxyOp(packageName, featureId) != AppOpsManager.MODE_ALLOWED) {
+                tryRespondWithError(listener, SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS);
+                return;
+            }
+            mSessionInProgress = true;
+            mRecordingInProgress = true;
+
+            mListener = listener;
+            mDelegatingListener = new DelegatingListener(listener, () -> {
+                // To be invoked in terminal calls of the callback: results() or error()
+                if (DEBUG) {
+                    Slog.i(TAG, "Recognition session complete");
+                }
+
+                synchronized (mLock) {
+                    resetStateLocked();
+                }
+            });
+            mPackageName = packageName;
+            mFeatureId = featureId;
+
+            run(service ->
+                    service.startListening(
+                            recognizerIntent,
+                            mDelegatingListener,
+                            packageName,
+                            featureId,
+                            mCallingUid));
+        }
+    }
+
+    void stopListening(
+            IRecognitionListener listener, String packageName, String featureId) {
         if (DEBUG) {
             Slog.i(TAG, "#stopListening for package: " + packageName + ", feature=" + featureId);
         }
-        run(service -> service.stopListening(listener, packageName, featureId));
+
+        if (!mConnected) {
+            tryRespondWithError(listener, SpeechRecognizer.ERROR_SERVER_DISCONNECTED);
+            return;
+        }
+
+        synchronized (mLock) {
+            if (mListener == null) {
+                Log.w(TAG, "#stopListening called with no preceding #startListening - ignoring");
+                tryRespondWithError(listener, SpeechRecognizer.ERROR_CLIENT);
+                return;
+            }
+
+            if (mListener.asBinder() != listener.asBinder()) {
+                Log.w(TAG, "#stopListening called with an unexpected listener");
+                tryRespondWithError(listener, SpeechRecognizer.ERROR_CLIENT);
+                return;
+            }
+
+            if (!mRecordingInProgress) {
+                Slog.i(TAG, "#stopListening called while listening isn't in progress, ignoring.");
+                return;
+            }
+            mRecordingInProgress = false;
+
+            finishProxyOp(packageName, featureId);
+
+            run(service -> service.stopListening(mDelegatingListener, packageName, featureId));
+        }
     }
 
-    void cancel(IRecognitionListener listener, String packageName, String featureId)
-            throws RemoteException {
+    void cancel(
+            IRecognitionListener listener,
+            String packageName,
+            String featureId,
+            boolean isShutdown) {
         if (DEBUG) {
             Slog.i(TAG, "#cancel for package: " + packageName + ", feature=" + featureId);
         }
-        run(service -> service.cancel(listener, packageName, featureId));
+
+        if (!mConnected) {
+            tryRespondWithError(listener, SpeechRecognizer.ERROR_SERVER_DISCONNECTED);
+        }
+
+        synchronized (mLock) {
+            if (mListener == null) {
+                if (DEBUG) {
+                    Log.w(TAG, "#cancel called with no preceding #startListening - ignoring");
+                }
+                return;
+            }
+
+            if (mListener.asBinder() != listener.asBinder()) {
+                Log.w(TAG, "#cancel called with an unexpected listener");
+                tryRespondWithError(listener, SpeechRecognizer.ERROR_CLIENT);
+                return;
+            }
+
+            // Temporary reference to allow for resetting the hard link mDelegatingListener to null.
+            IRecognitionListener delegatingListener = mDelegatingListener;
+
+            run(service -> service.cancel(delegatingListener, packageName, featureId, isShutdown));
+
+            if (mRecordingInProgress) {
+                finishProxyOp(packageName, featureId);
+            }
+            mRecordingInProgress = false;
+            mSessionInProgress = false;
+
+            mDelegatingListener = null;
+            mListener = null;
+
+            // Schedule to unbind after cancel is delivered.
+            if (isShutdown) {
+                run(service -> unbind());
+            }
+        }
+    }
+
+    void shutdown() {
+        synchronized (mLock) {
+            if (this.mListener == null) {
+                if (DEBUG) {
+                    Slog.i(TAG, "Package died, but session wasn't initialized. "
+                            + "Not invoking #cancel");
+                }
+                return;
+            }
+        }
+
+        cancel(mListener, mPackageName, mFeatureId, true /* isShutdown */);
     }
 
     @Override // from ServiceConnector.Impl
     protected void onServiceConnectionStatusChanged(
             IRecognitionService service, boolean connected) {
-        if (!DEBUG) {
-            return;
+        mConnected = connected;
+
+        if (DEBUG) {
+            if (connected) {
+                Slog.i(TAG, "Connected to speech recognition service");
+            } else {
+                Slog.w(TAG, "Disconnected from speech recognition service");
+            }
         }
 
-        if (connected) {
-            Slog.i(TAG, "Connected to ASR service");
-        } else {
-            Slog.w(TAG, "Disconnected from ASR service");
+        synchronized (mLock) {
+            if (!connected) {
+                if (mListener == null) {
+                    Slog.i(TAG, "Connection to speech recognition service lost, but no "
+                            + "#startListening has been invoked yet.");
+                    return;
+                }
+
+                tryRespondWithError(mListener, SpeechRecognizer.ERROR_SERVER_DISCONNECTED);
+
+                resetStateLocked();
+            }
         }
     }
 
@@ -90,4 +284,119 @@
     protected long getAutoDisconnectTimeoutMs() {
         return PERMANENT_BOUND_TIMEOUT_MS;
     }
+
+    private void resetStateLocked() {
+        if (mRecordingInProgress && mPackageName != null && mFeatureId != null) {
+            finishProxyOp(mPackageName, mFeatureId);
+        }
+
+        mListener = null;
+        mDelegatingListener = null;
+        mSessionInProgress = false;
+        mRecordingInProgress = false;
+    }
+
+    private int startProxyOp(String packageName, String featureId) {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            return mAppOpsManager.startProxyOp(
+                    RECORD_AUDIO_APP_OP,
+                    mCallingUid,
+                    packageName,
+                    featureId,
+                    APP_OP_MESSAGE);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    private void finishProxyOp(String packageName, String featureId) {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            mAppOpsManager.finishProxyOp(
+                    RECORD_AUDIO_APP_OP, mCallingUid, packageName, featureId);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    private static void tryRespondWithError(IRecognitionListener listener, int errorCode) {
+        if (DEBUG) {
+            Slog.i(TAG, "Responding with error " + errorCode);
+        }
+
+        try {
+            if (listener != null) {
+                listener.onError(errorCode);
+            }
+        } catch (RemoteException e) {
+            Slog.w(TAG,
+                    String.format("Failed to respond with an error %d to the client", errorCode),
+                    e);
+        }
+    }
+
+    private static class DelegatingListener extends IRecognitionListener.Stub {
+
+        private final IRecognitionListener mRemoteListener;
+        private final Runnable mOnSessionComplete;
+
+        DelegatingListener(IRecognitionListener listener, Runnable onSessionComplete) {
+            mRemoteListener = listener;
+            mOnSessionComplete = onSessionComplete;
+        }
+
+        @Override
+        public void onReadyForSpeech(Bundle params) throws RemoteException {
+            mRemoteListener.onReadyForSpeech(params);
+        }
+
+        @Override
+        public void onBeginningOfSpeech() throws RemoteException {
+            mRemoteListener.onBeginningOfSpeech();
+        }
+
+        @Override
+        public void onRmsChanged(float rmsdB) throws RemoteException {
+            mRemoteListener.onRmsChanged(rmsdB);
+        }
+
+        @Override
+        public void onBufferReceived(byte[] buffer) throws RemoteException {
+            mRemoteListener.onBufferReceived(buffer);
+        }
+
+        @Override
+        public void onEndOfSpeech() throws RemoteException {
+            mRemoteListener.onEndOfSpeech();
+        }
+
+        @Override
+        public void onError(int error) throws RemoteException {
+            if (DEBUG) {
+                Slog.i(TAG, String.format("Error %d during recognition session", error));
+            }
+            mOnSessionComplete.run();
+            mRemoteListener.onError(error);
+        }
+
+        @Override
+        public void onResults(Bundle results) throws RemoteException {
+            if (DEBUG) {
+                Slog.i(TAG, "#onResults invoked for a recognition session");
+            }
+            mOnSessionComplete.run();
+            mRemoteListener.onResults(results);
+        }
+
+        @Override
+        public void onPartialResults(Bundle results) throws RemoteException {
+            mRemoteListener.onPartialResults(results);
+        }
+
+        @Override
+        public void onEvent(int eventType, Bundle params) throws RemoteException {
+            mRemoteListener.onEvent(eventType, params);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/speech/SpeechRecognitionManagerService.java b/services/core/java/com/android/server/speech/SpeechRecognitionManagerService.java
index 592ba9e..dbe7354 100644
--- a/services/core/java/com/android/server/speech/SpeechRecognitionManagerService.java
+++ b/services/core/java/com/android/server/speech/SpeechRecognitionManagerService.java
@@ -18,7 +18,9 @@
 
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
+import android.content.ComponentName;
 import android.content.Context;
+import android.os.IBinder;
 import android.os.UserHandle;
 import android.speech.IRecognitionServiceManager;
 import android.speech.IRecognitionServiceManagerCallback;
@@ -42,6 +44,7 @@
 
     public SpeechRecognitionManagerService(@NonNull Context context) {
         super(context,
+                // TODO(b/176578753): think if we want to favor the particular service here.
                 new FrameworkResourcesServiceNameResolver(
                         context,
                         R.string.config_defaultOnDeviceSpeechRecognitionService),
@@ -63,11 +66,15 @@
     final class SpeechRecognitionManagerServiceStub extends IRecognitionServiceManager.Stub {
 
         @Override
-        public void createSession(IRecognitionServiceManagerCallback callback) {
+        public void createSession(
+                ComponentName componentName,
+                IBinder clientToken,
+                boolean onDevice,
+                IRecognitionServiceManagerCallback callback) {
             int userId = UserHandle.getCallingUserId();
             synchronized (mLock) {
                 SpeechRecognitionManagerServiceImpl service = getServiceForUserLocked(userId);
-                service.createSessionLocked(callback);
+                service.createSessionLocked(componentName, clientToken, onDevice, callback);
             }
         }
     }
diff --git a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
index bcaf174..2656a3d 100644
--- a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
@@ -24,30 +24,44 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
+import android.os.Binder;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.speech.IRecognitionListener;
 import android.speech.IRecognitionService;
 import android.speech.IRecognitionServiceManagerCallback;
+import android.speech.SpeechRecognizer;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.infra.AbstractPerUserSystemService;
 
+import com.google.android.collect.Sets;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
 final class SpeechRecognitionManagerServiceImpl extends
         AbstractPerUserSystemService<SpeechRecognitionManagerServiceImpl,
             SpeechRecognitionManagerService> {
-
     private static final String TAG = SpeechRecognitionManagerServiceImpl.class.getSimpleName();
 
+    private static final int MAX_CONCURRENT_CONNECTIONS_BY_CLIENT = 10;
+
+    private final Object mLock = new Object();
+
+    @NonNull
     @GuardedBy("mLock")
-    @Nullable
-    private RemoteSpeechRecognitionService mRemoteService;
+    private final Map<Integer, Set<RemoteSpeechRecognitionService>> mRemoteServicesByUid =
+            new HashMap<>();
 
     SpeechRecognitionManagerServiceImpl(
             @NonNull SpeechRecognitionManagerService master,
             @NonNull Object lock, @UserIdInt int userId, boolean disabled) {
         super(master, lock, userId);
-        updateRemoteServiceLocked();
     }
 
     @GuardedBy("mLock")
@@ -67,92 +81,196 @@
     @Override // from PerUserSystemService
     protected boolean updateLocked(boolean disabled) {
         final boolean enabledChanged = super.updateLocked(disabled);
-        updateRemoteServiceLocked();
         return enabledChanged;
     }
 
-    /**
-     * Updates the reference to the remote service.
-     */
-    @GuardedBy("mLock")
-    private void updateRemoteServiceLocked() {
-        if (mRemoteService != null) {
-            if (mMaster.debug) {
-                Slog.d(TAG, "updateRemoteService(): destroying old remote service");
-            }
-            mRemoteService.unbind();
-            mRemoteService = null;
+    void createSessionLocked(
+            ComponentName componentName,
+            IBinder clientToken,
+            boolean onDevice,
+            IRecognitionServiceManagerCallback callback) {
+        if (mMaster.debug) {
+            Slog.i(TAG, String.format("#createSessionLocked, component=%s, onDevice=%s",
+                    componentName, onDevice));
         }
-    }
 
-    void createSessionLocked(IRecognitionServiceManagerCallback callback) {
-        // TODO(b/176578753): check clients have record audio permission.
-        // TODO(b/176578753): verify caller package is the one supplied
+        ComponentName serviceComponent = componentName;
+        if (onDevice) {
+            serviceComponent = getOnDeviceComponentNameLocked();
+        }
 
-        RemoteSpeechRecognitionService service = ensureRemoteServiceLocked();
+        if (serviceComponent == null) {
+            tryRespondWithError(callback, SpeechRecognizer.ERROR_CLIENT);
+            return;
+        }
+
+        final int creatorCallingUid = Binder.getCallingUid();
+        Set<String> creatorPackageNames =
+                Sets.newArraySet(
+                        getContext().getPackageManager().getPackagesForUid(creatorCallingUid));
+
+        RemoteSpeechRecognitionService service = createService(creatorCallingUid, serviceComponent);
 
         if (service == null) {
-            tryRespondWithError(callback);
+            tryRespondWithError(callback, SpeechRecognizer.ERROR_TOO_MANY_REQUESTS);
             return;
         }
 
+        IBinder.DeathRecipient deathRecipient =
+                () -> handleClientDeath(creatorCallingUid, service, true /* invoke #cancel */);
+
+        try {
+            clientToken.linkToDeath(deathRecipient, 0);
+        } catch (RemoteException e) {
+            // RemoteException == binder already died, schedule disconnect anyway.
+            handleClientDeath(creatorCallingUid, service, true /* invoke #cancel */);
+        }
+
         service.connect().thenAccept(binderService -> {
             if (binderService != null) {
                 try {
                     callback.onSuccess(new IRecognitionService.Stub() {
                         @Override
-                        public void startListening(Intent recognizerIntent,
+                        public void startListening(
+                                Intent recognizerIntent,
                                 IRecognitionListener listener,
-                                String packageName, String featureId) throws RemoteException {
+                                String packageName,
+                                String featureId,
+                                int callingUid) throws RemoteException {
+                            verifyCallerIdentity(
+                                    creatorCallingUid, packageName, creatorPackageNames, listener);
+                            if (callingUid != creatorCallingUid) {
+                                listener.onError(SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS);
+                                return;
+                            }
+
                             service.startListening(
                                     recognizerIntent, listener, packageName, featureId);
                         }
 
                         @Override
-                        public void stopListening(IRecognitionListener listener,
+                        public void stopListening(
+                                IRecognitionListener listener,
                                 String packageName,
                                 String featureId) throws RemoteException {
+                            verifyCallerIdentity(
+                                    creatorCallingUid, packageName, creatorPackageNames, listener);
+
                             service.stopListening(listener, packageName, featureId);
                         }
 
                         @Override
-                        public void cancel(IRecognitionListener listener,
+                        public void cancel(
+                                IRecognitionListener listener,
                                 String packageName,
-                                String featureId) throws RemoteException {
-                            service.cancel(listener, packageName, featureId);
+                                String featureId,
+                                boolean isShutdown) throws RemoteException {
+                            verifyCallerIdentity(
+                                    creatorCallingUid, packageName, creatorPackageNames, listener);
+
+                            service.cancel(listener, packageName, featureId, isShutdown);
+
+                            if (isShutdown) {
+                                handleClientDeath(
+                                        creatorCallingUid,
+                                        service,
+                                        false /* invoke #cancel */);
+                                clientToken.unlinkToDeath(deathRecipient, 0);
+                            }
                         }
                     });
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Error creating a speech recognition session", e);
-                    tryRespondWithError(callback);
+                    tryRespondWithError(callback, SpeechRecognizer.ERROR_CLIENT);
                 }
             } else {
-                tryRespondWithError(callback);
+                tryRespondWithError(callback, SpeechRecognizer.ERROR_CLIENT);
             }
         });
     }
 
-    @GuardedBy("mLock")
-    @Nullable
-    private RemoteSpeechRecognitionService ensureRemoteServiceLocked() {
-        if (mRemoteService == null) {
-            final String serviceName = getComponentNameLocked();
-            if (serviceName == null) {
-                if (mMaster.verbose) {
-                    Slog.v(TAG, "ensureRemoteServiceLocked(): no service component name.");
-                }
-                return null;
-            }
-            final ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
-            mRemoteService =
-                    new RemoteSpeechRecognitionService(getContext(), serviceComponent, mUserId);
+    private void verifyCallerIdentity(
+            int creatorCallingUid,
+            String packageName,
+            Set<String> creatorPackageNames,
+            IRecognitionListener listener) throws RemoteException {
+        if (creatorCallingUid != Binder.getCallingUid()
+                || !creatorPackageNames.contains(packageName)) {
+            listener.onError(SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS);
         }
-        return mRemoteService;
     }
 
-    private static void tryRespondWithError(IRecognitionServiceManagerCallback callback) {
+    private void handleClientDeath(
+            int callingUid,
+            RemoteSpeechRecognitionService service, boolean invokeCancel) {
+        if (invokeCancel) {
+            service.shutdown();
+        }
+        removeService(callingUid, service);
+    }
+
+    @GuardedBy("mLock")
+    @Nullable
+    private ComponentName getOnDeviceComponentNameLocked() {
+        final String serviceName = getComponentNameLocked();
+        if (serviceName == null) {
+            if (mMaster.verbose) {
+                Slog.v(TAG, "ensureRemoteServiceLocked(): no service component name.");
+            }
+            return null;
+        }
+        return ComponentName.unflattenFromString(serviceName);
+    }
+
+    private RemoteSpeechRecognitionService createService(
+            int callingUid, ComponentName serviceComponent) {
+        synchronized (mLock) {
+            Set<RemoteSpeechRecognitionService> servicesForClient =
+                    mRemoteServicesByUid.get(callingUid);
+
+            if (servicesForClient != null
+                    && servicesForClient.size() >= MAX_CONCURRENT_CONNECTIONS_BY_CLIENT) {
+                return null;
+            }
+
+            if (servicesForClient != null) {
+                Optional<RemoteSpeechRecognitionService> existingService =
+                        servicesForClient
+                                .stream()
+                                .filter(service ->
+                                        service.getServiceComponentName().equals(serviceComponent))
+                                .findFirst();
+                if (existingService.isPresent()) {
+                    return existingService.get();
+                }
+            }
+
+            RemoteSpeechRecognitionService service =
+                    new RemoteSpeechRecognitionService(
+                            getContext(), serviceComponent, getUserId(), callingUid);
+
+            Set<RemoteSpeechRecognitionService> valuesByCaller =
+                    mRemoteServicesByUid.computeIfAbsent(callingUid, key -> new HashSet<>());
+            valuesByCaller.add(service);
+
+            return service;
+        }
+    }
+
+    private void removeService(int callingUid, RemoteSpeechRecognitionService service) {
+        synchronized (mLock) {
+            Set<RemoteSpeechRecognitionService> valuesByCaller =
+                    mRemoteServicesByUid.get(callingUid);
+            if (valuesByCaller != null) {
+                valuesByCaller.remove(service);
+            }
+        }
+    }
+
+    private static void tryRespondWithError(IRecognitionServiceManagerCallback callback,
+            int errorCode) {
         try {
-            callback.onError();
+            callback.onError(errorCode);
         } catch (RemoteException e) {
             Slog.w(TAG, "Failed to respond with error");
         }
diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java
index 3726407..6ad30b5 100644
--- a/services/core/java/com/android/server/vcn/Vcn.java
+++ b/services/core/java/com/android/server/vcn/Vcn.java
@@ -19,10 +19,12 @@
 import static com.android.server.VcnManagementService.VDBG;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.net.vcn.VcnConfig;
 import android.net.vcn.VcnGatewayConnectionConfig;
+import android.net.vcn.VcnManager.VcnErrorCode;
 import android.os.Handler;
 import android.os.Message;
 import android.os.ParcelUuid;
@@ -30,7 +32,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.annotations.VisibleForTesting.Visibility;
-import com.android.server.VcnManagementService.VcnSafemodeCallback;
+import com.android.server.VcnManagementService.VcnCallback;
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
 
 import java.util.Collections;
@@ -86,18 +88,18 @@
     private static final int MSG_CMD_TEARDOWN = MSG_CMD_BASE;
 
     /**
-     * Causes this VCN to immediately enter Safemode.
+     * Causes this VCN to immediately enter safe mode.
      *
-     * <p>Upon entering Safemode, the VCN will unregister its RequestListener, tear down all of its
-     * VcnGatewayConnections, and notify VcnManagementService that it is in Safemode.
+     * <p>Upon entering safe mode, the VCN will unregister its RequestListener, tear down all of its
+     * VcnGatewayConnections, and notify VcnManagementService that it is in safe mode.
      */
-    private static final int MSG_CMD_ENTER_SAFEMODE = MSG_CMD_BASE + 1;
+    private static final int MSG_CMD_ENTER_SAFE_MODE = MSG_CMD_BASE + 1;
 
     @NonNull private final VcnContext mVcnContext;
     @NonNull private final ParcelUuid mSubscriptionGroup;
     @NonNull private final Dependencies mDeps;
     @NonNull private final VcnNetworkRequestListener mRequestListener;
-    @NonNull private final VcnSafemodeCallback mVcnSafemodeCallback;
+    @NonNull private final VcnCallback mVcnCallback;
 
     @NonNull
     private final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> mVcnGatewayConnections =
@@ -125,14 +127,8 @@
             @NonNull ParcelUuid subscriptionGroup,
             @NonNull VcnConfig config,
             @NonNull TelephonySubscriptionSnapshot snapshot,
-            @NonNull VcnSafemodeCallback vcnSafemodeCallback) {
-        this(
-                vcnContext,
-                subscriptionGroup,
-                config,
-                snapshot,
-                vcnSafemodeCallback,
-                new Dependencies());
+            @NonNull VcnCallback vcnCallback) {
+        this(vcnContext, subscriptionGroup, config, snapshot, vcnCallback, new Dependencies());
     }
 
     @VisibleForTesting(visibility = Visibility.PRIVATE)
@@ -141,13 +137,12 @@
             @NonNull ParcelUuid subscriptionGroup,
             @NonNull VcnConfig config,
             @NonNull TelephonySubscriptionSnapshot snapshot,
-            @NonNull VcnSafemodeCallback vcnSafemodeCallback,
+            @NonNull VcnCallback vcnCallback,
             @NonNull Dependencies deps) {
         super(Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper());
         mVcnContext = vcnContext;
         mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
-        mVcnSafemodeCallback =
-                Objects.requireNonNull(vcnSafemodeCallback, "Missing vcnSafemodeCallback");
+        mVcnCallback = Objects.requireNonNull(vcnCallback, "Missing vcnCallback");
         mDeps = Objects.requireNonNull(deps, "Missing deps");
         mRequestListener = new VcnNetworkRequestListener();
 
@@ -216,8 +211,8 @@
             case MSG_CMD_TEARDOWN:
                 handleTeardown();
                 break;
-            case MSG_CMD_ENTER_SAFEMODE:
-                handleEnterSafemode();
+            case MSG_CMD_ENTER_SAFE_MODE:
+                handleEnterSafeMode();
                 break;
             default:
                 Slog.wtf(getLogTag(), "Unknown msg.what: " + msg.what);
@@ -243,10 +238,10 @@
         mIsActive.set(false);
     }
 
-    private void handleEnterSafemode() {
+    private void handleEnterSafeMode() {
         handleTeardown();
 
-        mVcnSafemodeCallback.onEnteredSafemode();
+        mVcnCallback.onEnteredSafeMode();
     }
 
     private void handleNetworkRequested(
@@ -335,14 +330,31 @@
     /** Callback used for passing status signals from a VcnGatewayConnection to its managing Vcn. */
     @VisibleForTesting(visibility = Visibility.PACKAGE)
     public interface VcnGatewayStatusCallback {
-        /** Called by a VcnGatewayConnection to indicate that it has entered Safemode. */
-        void onEnteredSafemode();
+        /** Called by a VcnGatewayConnection to indicate that it has entered safe mode. */
+        void onEnteredSafeMode();
+
+        /** Callback by a VcnGatewayConnection to indicate that an error occurred. */
+        void onGatewayConnectionError(
+                @NonNull int[] networkCapabilities,
+                @VcnErrorCode int errorCode,
+                @Nullable String exceptionClass,
+                @Nullable String exceptionMessage);
     }
 
     private class VcnGatewayStatusCallbackImpl implements VcnGatewayStatusCallback {
         @Override
-        public void onEnteredSafemode() {
-            sendMessage(obtainMessage(MSG_CMD_ENTER_SAFEMODE));
+        public void onEnteredSafeMode() {
+            sendMessage(obtainMessage(MSG_CMD_ENTER_SAFE_MODE));
+        }
+
+        @Override
+        public void onGatewayConnectionError(
+                @NonNull int[] networkCapabilities,
+                @VcnErrorCode int errorCode,
+                @Nullable String exceptionClass,
+                @Nullable String exceptionMessage) {
+            mVcnCallback.onGatewayConnectionError(
+                    networkCapabilities, errorCode, exceptionClass, exceptionMessage);
         }
     }
 
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 12590eb..9ee072e 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -22,6 +22,9 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.vcn.VcnManager.VCN_ERROR_CODE_CONFIG_ERROR;
+import static android.net.vcn.VcnManager.VCN_ERROR_CODE_INTERNAL_ERROR;
+import static android.net.vcn.VcnManager.VCN_ERROR_CODE_NETWORK_ERROR;
 
 import static com.android.server.VcnManagementService.VDBG;
 
@@ -52,7 +55,9 @@
 import android.net.ipsec.ike.IkeSessionCallback;
 import android.net.ipsec.ike.IkeSessionConfiguration;
 import android.net.ipsec.ike.IkeSessionParams;
+import android.net.ipsec.ike.exceptions.AuthenticationFailedException;
 import android.net.ipsec.ike.exceptions.IkeException;
+import android.net.ipsec.ike.exceptions.IkeInternalException;
 import android.net.ipsec.ike.exceptions.IkeProtocolException;
 import android.net.vcn.VcnGatewayConnectionConfig;
 import android.net.vcn.VcnTransportInfo;
@@ -418,13 +423,13 @@
     private static final int EVENT_SUBSCRIPTIONS_CHANGED = 9;
 
     /**
-     * Sent when this VcnGatewayConnection has entered Safemode.
+     * Sent when this VcnGatewayConnection has entered safe mode.
      *
-     * <p>A VcnGatewayConnection enters Safemode when it takes over {@link
+     * <p>A VcnGatewayConnection enters safe mode when it takes over {@link
      * #SAFEMODE_TIMEOUT_SECONDS} to enter {@link ConnectedState}.
      *
      * <p>When a VcnGatewayConnection enters safe mode, it will fire {@link
-     * VcnGatewayStatusCallback#onEnteredSafemode()} to notify its Vcn. The Vcn will then shut down
+     * VcnGatewayStatusCallback#onEnteredSafeMode()} to notify its Vcn. The Vcn will then shut down
      * its VcnGatewayConnectin(s).
      *
      * <p>Relevant in DisconnectingState, ConnectingState, ConnectedState (if the Vcn Network is not
@@ -432,7 +437,7 @@
      *
      * @param arg1 The "all" token; this signal is always honored.
      */
-    private static final int EVENT_SAFEMODE_TIMEOUT_EXCEEDED = 10;
+    private static final int EVENT_SAFE_MODE_TIMEOUT_EXCEEDED = 10;
 
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     @NonNull
@@ -551,7 +556,7 @@
     @Nullable private WakeupMessage mTeardownTimeoutAlarm;
     @Nullable private WakeupMessage mDisconnectRequestAlarm;
     @Nullable private WakeupMessage mRetryTimeoutAlarm;
-    @Nullable private WakeupMessage mSafemodeTimeoutAlarm;
+    @Nullable private WakeupMessage mSafeModeTimeoutAlarm;
 
     public VcnGatewayConnection(
             @NonNull VcnContext vcnContext,
@@ -638,7 +643,7 @@
         cancelTeardownTimeoutAlarm();
         cancelDisconnectRequestAlarm();
         cancelRetryTimeoutAlarm();
-        cancelSafemodeAlarm();
+        cancelSafeModeAlarm();
 
         mUnderlyingNetworkTracker.teardown();
     }
@@ -928,38 +933,91 @@
     }
 
     @VisibleForTesting(visibility = Visibility.PRIVATE)
-    void setSafemodeAlarm() {
+    void setSafeModeAlarm() {
         // Only schedule a NEW alarm if none is already set.
-        if (mSafemodeTimeoutAlarm != null) {
+        if (mSafeModeTimeoutAlarm != null) {
             return;
         }
 
-        final Message delayedMessage = obtainMessage(EVENT_SAFEMODE_TIMEOUT_EXCEEDED, TOKEN_ALL);
-        mSafemodeTimeoutAlarm =
+        final Message delayedMessage = obtainMessage(EVENT_SAFE_MODE_TIMEOUT_EXCEEDED, TOKEN_ALL);
+        mSafeModeTimeoutAlarm =
                 createScheduledAlarm(
                         SAFEMODE_TIMEOUT_ALARM,
                         delayedMessage,
                         TimeUnit.SECONDS.toMillis(SAFEMODE_TIMEOUT_SECONDS));
     }
 
-    private void cancelSafemodeAlarm() {
-        if (mSafemodeTimeoutAlarm != null) {
-            mSafemodeTimeoutAlarm.cancel();
-            mSafemodeTimeoutAlarm = null;
+    private void cancelSafeModeAlarm() {
+        if (mSafeModeTimeoutAlarm != null) {
+            mSafeModeTimeoutAlarm.cancel();
+            mSafeModeTimeoutAlarm = null;
         }
 
-        removeEqualMessages(EVENT_SAFEMODE_TIMEOUT_EXCEEDED);
+        removeEqualMessages(EVENT_SAFE_MODE_TIMEOUT_EXCEEDED);
     }
 
-    private void sessionLost(int token, @Nullable Exception exception) {
+    private void sessionLostWithoutCallback(int token, @Nullable Exception exception) {
         sendMessageAndAcquireWakeLock(
                 EVENT_SESSION_LOST, token, new EventSessionLostInfo(exception));
     }
 
+    private void sessionLost(int token, @Nullable Exception exception) {
+        // Only notify mGatewayStatusCallback if the session was lost with an error. All
+        // authentication and DNS failures are sent through
+        // IkeSessionCallback.onClosedExceptionally(), which calls sessionClosed()
+        if (exception != null) {
+            mGatewayStatusCallback.onGatewayConnectionError(
+                    mConnectionConfig.getRequiredUnderlyingCapabilities(),
+                    VCN_ERROR_CODE_INTERNAL_ERROR,
+                    "java.lang.RuntimeException",
+                    "Received "
+                            + exception.getClass().getSimpleName()
+                            + " with message: "
+                            + exception.getMessage());
+        }
+
+        sessionLostWithoutCallback(token, exception);
+    }
+
+    private void notifyStatusCallbackForSessionClosed(@NonNull Exception exception) {
+        final int errorCode;
+        final String exceptionClass;
+        final String exceptionMessage;
+
+        if (exception instanceof AuthenticationFailedException) {
+            errorCode = VCN_ERROR_CODE_CONFIG_ERROR;
+            exceptionClass = exception.getClass().getName();
+            exceptionMessage = exception.getMessage();
+        } else if (exception instanceof IkeInternalException
+                && exception.getCause() instanceof IOException) {
+            errorCode = VCN_ERROR_CODE_NETWORK_ERROR;
+            exceptionClass = "java.io.IOException";
+            exceptionMessage = exception.getCause().getMessage();
+        } else {
+            errorCode = VCN_ERROR_CODE_INTERNAL_ERROR;
+            exceptionClass = "java.lang.RuntimeException";
+            exceptionMessage =
+                    "Received "
+                            + exception.getClass().getSimpleName()
+                            + " with message: "
+                            + exception.getMessage();
+        }
+
+        mGatewayStatusCallback.onGatewayConnectionError(
+                mConnectionConfig.getRequiredUnderlyingCapabilities(),
+                errorCode,
+                exceptionClass,
+                exceptionMessage);
+    }
+
     private void sessionClosed(int token, @Nullable Exception exception) {
+        if (exception != null) {
+            notifyStatusCallbackForSessionClosed(exception);
+        }
+
         // SESSION_LOST MUST be sent before SESSION_CLOSED to ensure that the SM moves to the
         // Disconnecting state.
-        sessionLost(token, exception);
+        sessionLostWithoutCallback(token, exception);
         sendMessageAndAcquireWakeLock(EVENT_SESSION_CLOSED, token);
     }
 
@@ -1084,6 +1142,8 @@
         }
 
         protected void handleDisconnectRequested(String msg) {
+            // TODO(b/180526152): notify VcnStatusCallback for Network loss
+
             Slog.v(TAG, "Tearing down. Cause: " + msg);
             mIsRunning = false;
 
@@ -1125,7 +1185,7 @@
                 Slog.wtf(TAG, "Active IKE Session or NetworkAgent in DisconnectedState");
             }
 
-            cancelSafemodeAlarm();
+            cancelSafeModeAlarm();
         }
 
         @Override
@@ -1153,7 +1213,7 @@
         @Override
         protected void exitState() {
             // Safe to blindly set up, as it is cancelled and cleared on entering this state
-            setSafemodeAlarm();
+            setSafeModeAlarm();
         }
     }
 
@@ -1228,6 +1288,8 @@
 
                     String reason = ((EventDisconnectRequestedInfo) msg.obj).reason;
                     if (reason.equals(DISCONNECT_REASON_UNDERLYING_NETWORK_LOST)) {
+                        // TODO(b/180526152): notify VcnStatusCallback for Network loss
+
                         // Will trigger EVENT_SESSION_CLOSED immediately.
                         mIkeSession.kill();
                         break;
@@ -1245,9 +1307,9 @@
                         transitionTo(mDisconnectedState);
                     }
                     break;
-                case EVENT_SAFEMODE_TIMEOUT_EXCEEDED:
-                    mGatewayStatusCallback.onEnteredSafemode();
-                    mSafemodeTimeoutAlarm = null;
+                case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
+                    mGatewayStatusCallback.onEnteredSafeMode();
+                    mSafeModeTimeoutAlarm = null;
                     break;
                 default:
                     logUnhandledMessage(msg);
@@ -1331,9 +1393,9 @@
                 case EVENT_DISCONNECT_REQUESTED:
                     handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason);
                     break;
-                case EVENT_SAFEMODE_TIMEOUT_EXCEEDED:
-                    mGatewayStatusCallback.onEnteredSafemode();
-                    mSafemodeTimeoutAlarm = null;
+                case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
+                    mGatewayStatusCallback.onEnteredSafeMode();
+                    mSafeModeTimeoutAlarm = null;
                     break;
                 default:
                     logUnhandledMessage(msg);
@@ -1399,7 +1461,7 @@
 
             // Validated connection, clear failed attempt counter
             mFailedAttempts = 0;
-            cancelSafemodeAlarm();
+            cancelSafeModeAlarm();
         }
 
         protected void applyTransform(
@@ -1517,9 +1579,9 @@
                 case EVENT_DISCONNECT_REQUESTED:
                     handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason);
                     break;
-                case EVENT_SAFEMODE_TIMEOUT_EXCEEDED:
-                    mGatewayStatusCallback.onEnteredSafemode();
-                    mSafemodeTimeoutAlarm = null;
+                case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
+                    mGatewayStatusCallback.onEnteredSafeMode();
+                    mSafeModeTimeoutAlarm = null;
                     break;
                 default:
                     logUnhandledMessage(msg);
@@ -1573,9 +1635,8 @@
 
         @Override
         protected void exitState() {
-            // Attempt to set the safe mode alarm - this requires the Vcn Network being validated
-            // while in ConnectedState (which cancels the previous alarm)
-            setSafemodeAlarm();
+            // Will only set a new alarm if no safe mode alarm is currently scheduled.
+            setSafeModeAlarm();
         }
     }
 
@@ -1623,9 +1684,9 @@
                 case EVENT_DISCONNECT_REQUESTED:
                     handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason);
                     break;
-                case EVENT_SAFEMODE_TIMEOUT_EXCEEDED:
-                    mGatewayStatusCallback.onEnteredSafemode();
-                    mSafemodeTimeoutAlarm = null;
+                case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
+                    mGatewayStatusCallback.onEnteredSafeMode();
+                    mSafeModeTimeoutAlarm = null;
                     break;
                 default:
                     logUnhandledMessage(msg);
@@ -1710,8 +1771,6 @@
             }
         }
 
-        // TODO: Make a VcnNetworkSpecifier, and match all underlying subscription IDs.
-
         return builder.build();
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 2e98c2c..589a8c3 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -318,6 +318,7 @@
     public static final String DUMP_CONTAINERS_CMD = "containers";
     public static final String DUMP_RECENTS_CMD = "recents";
     public static final String DUMP_RECENTS_SHORT_CMD = "r";
+    public static final String DUMP_TOP_RESUMED_ACTIVITY = "top-resumed";
 
     /** This activity is not being relaunched, or being relaunched for a non-resize reason. */
     public static final int RELAUNCH_REASON_NONE = 0;
@@ -3756,6 +3757,14 @@
         }
     }
 
+    void dumpTopResumedActivityLocked(PrintWriter pw) {
+        pw.println("ACTIVITY MANAGER TOP-RESUMED (dumpsys activity top-resumed)");
+        ActivityRecord topRecord = mRootWindowContainer.getTopResumedActivity();
+        if (topRecord != null) {
+            topRecord.dump(pw, "", true);
+        }
+    }
+
     void dumpActivitiesLocked(FileDescriptor fd, PrintWriter pw, String[] args,
             int opti, boolean dumpAll, boolean dumpClient, String dumpPackage) {
         dumpActivitiesLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage,
@@ -5896,6 +5905,8 @@
                     if (getRecentTasks() != null) {
                         getRecentTasks().dump(pw, dumpAll, dumpPackage);
                     }
+                } else if (DUMP_TOP_RESUMED_ACTIVITY.equals(cmd)) {
+                    dumpTopResumedActivityLocked(pw);
                 }
             }
         }
diff --git a/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
new file mode 100644
index 0000000..5d6d513
--- /dev/null
+++ b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_REMOTE_ANIMATIONS;
+import static com.android.server.wm.AnimationAdapterProto.REMOTE;
+import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
+
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.util.proto.ProtoOutputStream;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.server.policy.WindowManagerPolicy;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+class NonAppWindowAnimationAdapter implements AnimationAdapter {
+
+    private final WindowState mWindow;
+    private RemoteAnimationTarget mTarget;
+    private SurfaceControl mCapturedLeash;
+
+    private long mDurationHint;
+    private long mStatusBarTransitionDelay;
+
+    @Override
+    public boolean getShowWallpaper() {
+        return false;
+    }
+
+    NonAppWindowAnimationAdapter(WindowState w,
+            long durationHint, long statusBarTransitionDelay) {
+        mWindow = w;
+        mDurationHint = durationHint;
+        mStatusBarTransitionDelay = statusBarTransitionDelay;
+    }
+
+    /**
+     * Creates and starts remote animations for all the visible non app windows.
+     *
+     * @return RemoteAnimationTarget[] targets for all the visible non app windows
+     */
+    public static RemoteAnimationTarget[] startNonAppWindowAnimationsForKeyguardExit(
+            WindowManagerService service, long durationHint, long statusBarTransitionDelay) {
+        final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>();
+
+        final WindowManagerPolicy policy = service.mPolicy;
+        service.mRoot.forAllWindows(nonAppWindow -> {
+            if (nonAppWindow.mActivityRecord == null && policy.canBeHiddenByKeyguardLw(nonAppWindow)
+                    && nonAppWindow.wouldBeVisibleIfPolicyIgnored() && !nonAppWindow.isVisible()) {
+                final NonAppWindowAnimationAdapter nonAppAdapter = new NonAppWindowAnimationAdapter(
+                        nonAppWindow, durationHint, statusBarTransitionDelay);
+                nonAppWindow.startAnimation(nonAppWindow.getPendingTransaction(),
+                        nonAppAdapter, false /* hidden */, ANIMATION_TYPE_WINDOW_ANIMATION);
+                targets.add(nonAppAdapter.createRemoteAnimationTarget());
+            }
+        }, true /* traverseTopToBottom */);
+        return targets.toArray(new RemoteAnimationTarget[targets.size()]);
+    }
+
+    /**
+     * Create a remote animation target for this animation adapter.
+     */
+    RemoteAnimationTarget createRemoteAnimationTarget() {
+        mTarget = new RemoteAnimationTarget(-1, -1, getLeash(), false,
+                new Rect(), null, mWindow.getPrefixOrderIndex(), mWindow.getLastSurfacePosition(),
+                mWindow.getBounds(), null, mWindow.getWindowConfiguration(), true, null, null,
+                null);
+        return mTarget;
+    }
+
+    @Override
+    public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t,
+            int type, SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
+        mCapturedLeash = animationLeash;
+    }
+
+    @Override
+    public long getDurationHint() {
+        return mDurationHint;
+    }
+
+    @Override
+    public long getStatusBarTransitionsStartTime() {
+        return SystemClock.uptimeMillis() + mStatusBarTransitionDelay;
+    }
+
+    /**
+     * @return the leash for this animation (only valid after the non app window surface animation
+     * has started).
+     */
+    SurfaceControl getLeash() {
+        return mCapturedLeash;
+    }
+
+    @Override
+    public void onAnimationCancelled(SurfaceControl animationLeash) {
+        ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "onAnimationCancelled");
+    }
+
+    @Override
+    public void dump(PrintWriter pw, String prefix) {
+        pw.print(prefix);
+        pw.print("token=");
+        pw.println(mWindow.mToken);
+        if (mTarget != null) {
+            pw.print(prefix);
+            pw.println("Target:");
+            mTarget.dump(pw, prefix + "  ");
+        } else {
+            pw.print(prefix);
+            pw.println("Target: null");
+        }
+    }
+
+    @Override
+    public void dumpDebug(ProtoOutputStream proto) {
+        final long token = proto.start(REMOTE);
+        if (mTarget != null) {
+            mTarget.dumpDebug(proto, TARGET);
+        }
+        proto.end(token);
+    }
+}
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index 392f27e..42cb96f 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -16,6 +16,9 @@
 
 package com.android.server.wm;
 
+import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
+import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
+
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_REMOTE_ANIMATIONS;
 import static com.android.server.wm.AnimationAdapterProto.REMOTE;
 import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
@@ -126,7 +129,7 @@
         final RemoteAnimationTarget[] wallpaperTargets = createWallpaperAnimations();
 
         // TODO(bc-unlock): Create the remote non app animation targets (if any)
-        final RemoteAnimationTarget[] nonAppTargets = new RemoteAnimationTarget[0];
+        final RemoteAnimationTarget[] nonAppTargets = createNonAppWindowAnimations(transit);
 
         mService.mAnimator.addAfterPrepareSurfacesRunnable(() -> {
             try {
@@ -215,6 +218,17 @@
                 }, mPendingWallpaperAnimations);
     }
 
+    private RemoteAnimationTarget[] createNonAppWindowAnimations(
+            @WindowManager.TransitionOldType int transit) {
+        ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "createNonAppWindowAnimations()");
+        return (transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY
+                || transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER)
+                ? NonAppWindowAnimationAdapter.startNonAppWindowAnimationsForKeyguardExit(mService,
+                    mRemoteAnimationAdapter.getDuration(),
+                    mRemoteAnimationAdapter.getStatusBarTransitionDelay())
+                : new RemoteAnimationTarget[0];
+    }
+
     private void onAnimationFinished() {
         ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "onAnimationFinished(): mPendingAnimations=%d",
                 mPendingAnimations.size());
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 66d5c77..d360916 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5301,7 +5301,8 @@
      */
     void onWindowFocusChanged(boolean hasFocus) {
         updateShadowsRadius(hasFocus, getSyncTransaction());
-        dispatchTaskInfoChangedIfNeeded(false /* force */);
+        // TODO(b/180525887): Un-comment once there is resolution on the bug.
+        // dispatchTaskInfoChangedIfNeeded(false /* force */);
     }
 
     void onPictureInPictureParamsChanged() {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index e76597d..48ae8d6 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -138,10 +138,12 @@
     private static final String TAG_ENROLLMENT_SPECIFIC_ID = "enrollment-specific-id";
     private static final String TAG_ADMIN_CAN_GRANT_SENSORS_PERMISSIONS =
             "admin-can-grant-sensors-permissions";
+    private static final String TAG_NETWORK_SLICING_ENABLED = "network-slicing-enabled";
     private static final String TAG_USB_DATA_SIGNALING = "usb-data-signaling";
     private static final String ATTR_VALUE = "value";
     private static final String ATTR_LAST_NETWORK_LOGGING_NOTIFICATION = "last-notification";
     private static final String ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS = "num-notifications";
+    private static final boolean NETWORK_SLICING_ENABLED_DEFAULT = true;
 
     DeviceAdminInfo info;
 
@@ -281,6 +283,7 @@
     public String mOrganizationId;
     public String mEnrollmentSpecificId;
     public boolean mAdminCanGrantSensorsPermissions;
+    public boolean mNetworkSlicingEnabled = NETWORK_SLICING_ENABLED_DEFAULT;
 
     private static final boolean USB_DATA_SIGNALING_ENABLED_DEFAULT = true;
     boolean mUsbDataSignalingEnabled = USB_DATA_SIGNALING_ENABLED_DEFAULT;
@@ -552,6 +555,9 @@
         }
         writeAttributeValueToXml(out, TAG_ADMIN_CAN_GRANT_SENSORS_PERMISSIONS,
                 mAdminCanGrantSensorsPermissions);
+        if (mNetworkSlicingEnabled != NETWORK_SLICING_ENABLED_DEFAULT) {
+            writeAttributeValueToXml(out, TAG_NETWORK_SLICING_ENABLED, mNetworkSlicingEnabled);
+        }
         if (mUsbDataSignalingEnabled != USB_DATA_SIGNALING_ENABLED_DEFAULT) {
             writeAttributeValueToXml(out, TAG_USB_DATA_SIGNALING, mUsbDataSignalingEnabled);
         }
@@ -784,6 +790,9 @@
                 mAlwaysOnVpnPackage = parser.getAttributeValue(null, ATTR_VALUE);
             } else if (TAG_ALWAYS_ON_VPN_LOCKDOWN.equals(tag)) {
                 mAlwaysOnVpnLockdown = parser.getAttributeBoolean(null, ATTR_VALUE, false);
+            } else if (TAG_NETWORK_SLICING_ENABLED.equals(tag)) {
+                mNetworkSlicingEnabled = parser.getAttributeBoolean(
+                        null, ATTR_VALUE, NETWORK_SLICING_ENABLED_DEFAULT);
             } else if (TAG_COMMON_CRITERIA_MODE.equals(tag)) {
                 mCommonCriteriaMode = parser.getAttributeBoolean(null, ATTR_VALUE, false);
             } else if (TAG_PASSWORD_COMPLEXITY.equals(tag)) {
@@ -1146,6 +1155,9 @@
         pw.print("mAlwaysOnVpnLockdown=");
         pw.println(mAlwaysOnVpnLockdown);
 
+        pw.print("mNetworkSlicingEnabled=");
+        pw.println(mNetworkSlicingEnabled);
+
         pw.print("mCommonCriteriaMode=");
         pw.println(mCommonCriteriaMode);
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index c688487..dd599c9 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -247,6 +247,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.storage.StorageManager;
+import android.permission.AdminPermissionControlParams;
 import android.permission.IPermissionManager;
 import android.permission.PermissionControllerManager;
 import android.provider.CalendarContract;
@@ -533,8 +534,10 @@
 
     /**
      * Strings logged with {@link
-     * com.android.internal.logging.nano.MetricsProto.MetricsEvent#PROVISIONING_ENTRY_POINT_ADB}
-     * and {@link DevicePolicyEnums#PROVISIONING_ENTRY_POINT_ADB}.
+     * com.android.internal.logging.nano.MetricsProto.MetricsEvent#PROVISIONING_ENTRY_POINT_ADB},
+     * {@link DevicePolicyEnums#PROVISIONING_ENTRY_POINT_ADB},
+     * {@link DevicePolicyEnums#SET_NETWORK_LOGGING_ENABLED} and
+     * {@link DevicePolicyEnums#RETRIEVE_NETWORK_LOGS}.
      */
     private static final String LOG_TAG_PROFILE_OWNER = "profile-owner";
     private static final String LOG_TAG_DEVICE_OWNER = "device-owner";
@@ -6450,7 +6453,7 @@
     private void forceWipeUser(int userId, String wipeReasonForUser, boolean wipeSilently) {
         boolean success = false;
         try {
-            if (getCurrentForegroundUser() == userId) {
+            if (getCurrentForegroundUserId() == userId) {
                 mInjector.getIActivityManager().switchUser(UserHandle.USER_SYSTEM);
             }
 
@@ -7891,6 +7894,14 @@
             updateDeviceOwnerLocked();
             setDeviceOwnershipSystemPropertyLocked();
 
+            //TODO(b/180371154): when provisionFullyManagedDevice is used in tests, remove this
+            // hard-coded default value setting.
+            if (isAdb(caller)) {
+                activeAdmin.mAdminCanGrantSensorsPermissions = true;
+                mPolicyCache.setAdminCanGrantSensorsPermissions(userId, true);
+                saveSettingsLocked(userId);
+            }
+
             mInjector.binderWithCleanCallingIdentity(() -> {
                 // Restrict adding a managed profile when a device owner is set on the device.
                 // That is to prevent the co-existence of a managed profile and a device owner
@@ -7910,7 +7921,7 @@
             Slog.i(LOG_TAG, "Device owner set: " + admin + " on user " + userId);
 
             if (mInjector.userManagerIsHeadlessSystemUserMode()) {
-                int currentForegroundUser = getCurrentForegroundUser();
+                int currentForegroundUser = getCurrentForegroundUserId();
                 Slog.i(LOG_TAG, "setDeviceOwner(): setting " + admin
                         + " as profile owner on user " + currentForegroundUser);
                 // Sets profile owner on current foreground user since
@@ -9041,7 +9052,7 @@
         return UserHandle.isSameApp(caller.getUid(), Process.SHELL_UID);
     }
 
-    private @UserIdInt int getCurrentForegroundUser() {
+    private @UserIdInt int getCurrentForegroundUserId() {
         try {
             return mInjector.getIActivityManager().getCurrentUser().id;
         } catch (RemoteException e) {
@@ -9050,6 +9061,25 @@
         return UserHandle.USER_NULL;
     }
 
+    @Override
+    public List<UserHandle> listForegroundAffiliatedUsers() {
+        checkIsDeviceOwner(getCallerIdentity());
+
+        int userId = mInjector.binderWithCleanCallingIdentity(() -> getCurrentForegroundUserId());
+
+        boolean isAffiliated;
+        synchronized (getLockObject()) {
+            isAffiliated = isUserAffiliatedWithDeviceLocked(userId);
+        }
+
+        if (!isAffiliated) return Collections.emptyList();
+
+        List<UserHandle> users = new ArrayList<>(1);
+        users.add(UserHandle.of(userId));
+
+        return users;
+    }
+
     protected int getProfileParentId(int userHandle) {
         return mInjector.binderWithCleanCallingIdentity(() -> {
             UserInfo parentUser = mUserManager.getProfileParent(userHandle);
@@ -11133,6 +11163,50 @@
     }
 
     @Override
+    public void setNetworkSlicingEnabled(boolean enabled) {
+        if (!mHasFeature) {
+            return;
+        }
+
+        final CallerIdentity caller = getCallerIdentity();
+        Preconditions.checkCallAuthorization(isProfileOwner(caller),
+                "Caller is not profile owner; only profile owner may control the network slicing");
+
+        synchronized (getLockObject()) {
+            final ActiveAdmin requiredAdmin = getProfileOwnerAdminLocked(
+                    caller.getUserId());
+            if (requiredAdmin != null && requiredAdmin.mNetworkSlicingEnabled != enabled) {
+                requiredAdmin.mNetworkSlicingEnabled = enabled;
+                saveSettingsLocked(caller.getUserId());
+                // TODO(b/178655595) notify CS the change.
+                // TODO(b/178655595) DevicePolicyEventLogger metrics
+            }
+        }
+    }
+
+    @Override
+    public boolean isNetworkSlicingEnabled(int userHandle) {
+        if (!mHasFeature) {
+            return false;
+        }
+
+        final CallerIdentity caller = getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
+        Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
+                permission.READ_NETWORK_DEVICE_CONFIG) || isProfileOwner(caller),
+                        "Caller is not profile owner and not granted"
+                                + " READ_NETWORK_DEVICE_CONFIG permission");
+        synchronized (getLockObject()) {
+            final ActiveAdmin requiredAdmin = getProfileOwnerAdminLocked(userHandle);
+            if (requiredAdmin != null) {
+                return requiredAdmin.mNetworkSlicingEnabled;
+            } else {
+                return false;
+            }
+        }
+    }
+
+    @Override
     public void setLockTaskPackages(ComponentName who, String[] packages)
             throws SecurityException {
         Objects.requireNonNull(who, "ComponentName is null");
@@ -12642,9 +12716,12 @@
                 if (grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED
                         || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED
                         || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT) {
+                    AdminPermissionControlParams permissionParams =
+                            new AdminPermissionControlParams(packageName, permission, grantState,
+                                    canAdminGrantSensorsPermissionsForUser(caller.getUserId()));
                     mInjector.getPermissionControllerManager(caller.getUserHandle())
                             .setRuntimePermissionGrantStateByDeviceAdmin(caller.getPackageName(),
-                                    packageName, permission, grantState, mContext.getMainExecutor(),
+                                    permissionParams, mContext.getMainExecutor(),
                                     (permissionWasSet) -> {
                                         if (isPostQAdmin && !permissionWasSet) {
                                             callback.sendResult(null);
@@ -12866,7 +12943,7 @@
                     return CODE_NONSYSTEM_USER_EXISTS;
                 }
 
-                int currentForegroundUser = getCurrentForegroundUser();
+                int currentForegroundUser = getCurrentForegroundUserId();
                 if (callingUserId != currentForegroundUser
                         && mInjector.userManagerIsHeadlessSystemUserMode()
                         && currentForegroundUser == UserHandle.USER_SYSTEM) {
@@ -12962,6 +13039,11 @@
         return CODE_OK;
     }
 
+    private void checkIsDeviceOwner(CallerIdentity caller) {
+        Preconditions.checkCallAuthorization(isDeviceOwner(caller), caller.getUid()
+                + " is not device owner");
+    }
+
     private ComponentName getOwnerComponent(String packageName, int userId) {
         if (isDeviceOwnerPackage(packageName, userId)) {
             return mOwners.getDeviceOwnerComponent();
@@ -14158,9 +14240,10 @@
             return;
         }
         final CallerIdentity caller = getCallerIdentity(admin, packageName);
+        final boolean isManagedProfileOwner = isProfileOwner(caller)
+                && isManagedProfile(caller.getUserId());
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                && (isDeviceOwner(caller)
-                || (isProfileOwner(caller) && isManagedProfile(caller.getUserId()))))
+                && (isDeviceOwner(caller) || isManagedProfileOwner))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING)));
 
         synchronized (getLockObject()) {
@@ -14182,6 +14265,8 @@
                     .setAdmin(caller.getPackageName())
                     .setBoolean(/* isDelegate */ admin == null)
                     .setInt(enabled ? 1 : 0)
+                    .setStrings(isManagedProfileOwner
+                            ? LOG_TAG_PROFILE_OWNER : LOG_TAG_DEVICE_OWNER)
                     .write();
         }
     }
@@ -14338,9 +14423,10 @@
             return null;
         }
         final CallerIdentity caller = getCallerIdentity(admin, packageName);
+        final boolean isManagedProfileOwner = isProfileOwner(caller)
+                && isManagedProfile(caller.getUserId());
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                &&  (isDeviceOwner(caller)
-                || (isProfileOwner(caller) && isManagedProfile(caller.getUserId()))))
+                &&  (isDeviceOwner(caller) || isManagedProfileOwner))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING)));
         if (mOwners.hasDeviceOwner()) {
             checkAllUsersAreAffiliatedWithDevice();
@@ -14354,6 +14440,8 @@
                     .createEvent(DevicePolicyEnums.RETRIEVE_NETWORK_LOGS)
                     .setAdmin(caller.getPackageName())
                     .setBoolean(/* isDelegate */ admin == null)
+                    .setStrings(isManagedProfileOwner
+                            ? LOG_TAG_PROFILE_OWNER : LOG_TAG_DEVICE_OWNER)
                     .write();
 
             final long currentTime = System.currentTimeMillis();
@@ -15452,7 +15540,7 @@
     private boolean isLockTaskFeatureEnabled(int lockTaskFeature) throws RemoteException {
         //TODO(b/175285301): Explicitly get the user's identity to check.
         int lockTaskFeatures =
-                getUserData(getCurrentForegroundUser()).mLockTaskFeatures;
+                getUserData(getCurrentForegroundUserId()).mLockTaskFeatures;
         return (lockTaskFeatures & lockTaskFeature) == lockTaskFeature;
     }
 
@@ -16708,6 +16796,8 @@
     @Override
     public boolean canUsbDataSignalingBeDisabled() {
         return mInjector.binderWithCleanCallingIdentity(() ->
-                mInjector.getUsbManager().getUsbHalVersion() >= UsbManager.USB_HAL_V1_3);
+                mInjector.getUsbManager() != null
+                        && mInjector.getUsbManager().getUsbHalVersion() >= UsbManager.USB_HAL_V1_3
+        );
     }
 }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index dd2dd81..2b09d12 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -326,8 +326,6 @@
             "com.android.server.musicrecognition.MusicRecognitionManagerService";
     private static final String SYSTEM_CAPTIONS_MANAGER_SERVICE_CLASS =
             "com.android.server.systemcaptions.SystemCaptionsManagerService";
-    private static final String TEXT_TO_SPEECH_MANAGER_SERVICE_CLASS =
-            "com.android.server.texttospeech.TextToSpeechManagerService";
     private static final String TIME_ZONE_RULES_MANAGER_SERVICE_CLASS =
             "com.android.server.timezone.RulesManagerService$Lifecycle";
     private static final String IOT_SERVICE_CLASS =
@@ -1715,7 +1713,6 @@
             startAttentionService(context, t);
             startRotationResolverService(context, t);
             startSystemCaptionsManagerService(context, t);
-            startTextToSpeechManagerService(context, t);
 
             // System Speech Recognition Service
             if (deviceHasConfigString(context,
@@ -2921,13 +2918,6 @@
         t.traceEnd();
     }
 
-    private void startTextToSpeechManagerService(@NonNull Context context,
-            @NonNull TimingsTraceAndSlog t) {
-        t.traceBegin("StartTextToSpeechManagerService");
-        mSystemServiceManager.startService(TEXT_TO_SPEECH_MANAGER_SERVICE_CLASS);
-        t.traceEnd();
-    }
-
     private void startContentCaptureService(@NonNull Context context,
             @NonNull TimingsTraceAndSlog t) {
         // First check if it was explicitly enabled by DeviceConfig
diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java
index 9666337..e7d0121 100644
--- a/services/people/java/com/android/server/people/PeopleService.java
+++ b/services/people/java/com/android/server/people/PeopleService.java
@@ -23,6 +23,7 @@
 import android.app.ActivityManager;
 import android.app.people.ConversationChannel;
 import android.app.people.ConversationStatus;
+import android.app.people.IConversationListener;
 import android.app.people.IPeopleManager;
 import android.app.prediction.AppPredictionContext;
 import android.app.prediction.AppPredictionSessionId;
@@ -33,10 +34,12 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ParceledListSlice;
+import android.content.pm.ShortcutInfo;
 import android.os.Binder;
 import android.os.CancellationSignal;
 import android.os.IBinder;
 import android.os.Process;
+import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.ArrayMap;
@@ -47,6 +50,7 @@
 import com.android.server.SystemService;
 import com.android.server.people.data.DataManager;
 
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Consumer;
@@ -58,7 +62,9 @@
 
     private static final String TAG = "PeopleService";
 
-    private final DataManager mDataManager;
+    private DataManager mDataManager;
+    @VisibleForTesting
+    ConversationListenerHelper mConversationListenerHelper;
 
     private PackageManagerInternal mPackageManagerInternal;
 
@@ -71,6 +77,8 @@
         super(context);
 
         mDataManager = new DataManager(context);
+        mConversationListenerHelper = new ConversationListenerHelper();
+        mDataManager.addConversationsListener(mConversationListenerHelper);
     }
 
     @Override
@@ -148,12 +156,14 @@
      * @param message used as message if SecurityException is thrown
      * @throws SecurityException if the caller is not system or root
      */
-    private static void enforceSystemRootOrSystemUI(Context context, String message) {
+    @VisibleForTesting
+    protected void enforceSystemRootOrSystemUI(Context context, String message) {
         if (isSystemOrRoot()) return;
         context.enforceCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
                 message);
     }
 
+    @VisibleForTesting
     final IBinder mService = new IPeopleManager.Stub() {
 
         @Override
@@ -241,8 +251,137 @@
             return new ParceledListSlice<>(
                     mDataManager.getStatuses(packageName, userId, conversationId));
         }
+
+        @Override
+        public void registerConversationListener(
+                String packageName, int userId, String shortcutId, IConversationListener listener) {
+            enforceSystemRootOrSystemUI(getContext(), "register conversation listener");
+            mConversationListenerHelper.addConversationListener(
+                    new ListenerKey(packageName, userId, shortcutId), listener);
+        }
+
+        @Override
+        public void unregisterConversationListener(IConversationListener listener) {
+            enforceSystemRootOrSystemUI(getContext(), "unregister conversation listener");
+            mConversationListenerHelper.removeConversationListener(listener);
+        }
     };
 
+    /**
+     * Listeners for conversation changes.
+     *
+     * @hide
+     */
+    public interface ConversationsListener {
+        /**
+         * Triggers with the list of modified conversations from {@link DataManager} for dispatching
+         * relevant updates to clients.
+         *
+         * @param conversations The conversations with modified data
+         * @see IPeopleManager#registerConversationListener(String, int, String,
+         * android.app.people.ConversationListener)
+         */
+        default void onConversationsUpdate(@NonNull List<ConversationChannel> conversations) {
+        }
+    }
+
+    /**
+     * Implements {@code ConversationListenerHelper} to dispatch conversation updates to registered
+     * clients.
+     */
+    public static class ConversationListenerHelper implements ConversationsListener {
+
+        ConversationListenerHelper() {
+        }
+
+        @VisibleForTesting
+        final RemoteCallbackList<IConversationListener> mListeners =
+                new RemoteCallbackList<>();
+
+        /** Adds {@code listener} with {@code key} associated. */
+        public synchronized void addConversationListener(ListenerKey key,
+                IConversationListener listener) {
+            mListeners.unregister(listener);
+            mListeners.register(listener, key);
+        }
+
+        /** Removes {@code listener}. */
+        public synchronized void removeConversationListener(
+                IConversationListener listener) {
+            mListeners.unregister(listener);
+        }
+
+        @Override
+        /** Dispatches updates to {@code mListeners} with keys mapped to {@code conversations}. */
+        public void onConversationsUpdate(List<ConversationChannel> conversations) {
+            int count = mListeners.beginBroadcast();
+            // Early opt-out if no listeners are registered.
+            if (count == 0) {
+                return;
+            }
+            Map<ListenerKey, ConversationChannel> keyedConversations = new HashMap<>();
+            for (ConversationChannel conversation : conversations) {
+                keyedConversations.put(getListenerKey(conversation), conversation);
+            }
+            for (int i = 0; i < count; i++) {
+                final ListenerKey listenerKey = (ListenerKey) mListeners.getBroadcastCookie(i);
+                if (!keyedConversations.containsKey(listenerKey)) {
+                    continue;
+                }
+                final IConversationListener listener = mListeners.getBroadcastItem(i);
+                try {
+                    ConversationChannel channel = keyedConversations.get(listenerKey);
+                    listener.onConversationUpdate(channel);
+                } catch (RemoteException e) {
+                    // The RemoteCallbackList will take care of removing the dead object.
+                }
+            }
+            mListeners.finishBroadcast();
+        }
+
+        private ListenerKey getListenerKey(ConversationChannel conversation) {
+            ShortcutInfo info = conversation.getShortcutInfo();
+            return new ListenerKey(info.getPackage(), info.getUserId(),
+                    info.getId());
+        }
+    }
+
+    private static class ListenerKey {
+        private final String mPackageName;
+        private final Integer mUserId;
+        private final String mShortcutId;
+
+        ListenerKey(String packageName, Integer userId, String shortcutId) {
+            this.mPackageName = packageName;
+            this.mUserId = userId;
+            this.mShortcutId = shortcutId;
+        }
+
+        public String getPackageName() {
+            return mPackageName;
+        }
+
+        public Integer getUserId() {
+            return mUserId;
+        }
+
+        public String getShortcutId() {
+            return mShortcutId;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            ListenerKey key = (ListenerKey) o;
+            return key.getPackageName().equals(mPackageName) && key.getUserId() == mUserId
+                    && key.getShortcutId().equals(mShortcutId);
+        }
+
+        @Override
+        public int hashCode() {
+            return mPackageName.hashCode() + mUserId.hashCode() + mShortcutId.hashCode();
+        }
+    }
+
     @VisibleForTesting
     final class LocalService extends PeopleServiceInternal {
 
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 1048dfd..75614d6 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -48,6 +48,7 @@
 import android.net.Uri;
 import android.os.CancellationSignal;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -75,14 +76,17 @@
 import com.android.server.LocalServices;
 import com.android.server.notification.NotificationManagerInternal;
 import com.android.server.notification.ShortcutHelper;
+import com.android.server.people.PeopleService;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.PriorityQueue;
 import java.util.Set;
 import java.util.concurrent.Executor;
@@ -118,6 +122,11 @@
     private final SparseArray<ScheduledFuture<?>> mUsageStatsQueryFutures = new SparseArray<>();
     private final SparseArray<NotificationListener> mNotificationListeners = new SparseArray<>();
     private final SparseArray<PackageMonitor> mPackageMonitors = new SparseArray<>();
+    @GuardedBy("mLock")
+    private final List<PeopleService.ConversationsListener> mConversationsListeners =
+            new ArrayList<>(1);
+    private final Handler mHandler;
+
     private ContentObserver mCallLogContentObserver;
     private ContentObserver mMmsSmsContentObserver;
 
@@ -128,14 +137,14 @@
     private ConversationStatusExpirationBroadcastReceiver mStatusExpReceiver;
 
     public DataManager(Context context) {
-        this(context, new Injector());
+        this(context, new Injector(), BackgroundThread.get().getLooper());
     }
 
-    @VisibleForTesting
-    DataManager(Context context, Injector injector) {
+    DataManager(Context context, Injector injector, Looper looper) {
         mContext = context;
         mInjector = injector;
         mScheduledExecutor = mInjector.createScheduledExecutor();
+        mHandler = new Handler(looper);
     }
 
     /** Initialization. Called when the system services are up running. */
@@ -234,20 +243,19 @@
             PackageData packageData = userData.getPackageData(packageName);
             // App may have been uninstalled.
             if (packageData != null) {
-                return getConversationChannel(packageData, shortcutId);
+                ConversationInfo conversationInfo = packageData.getConversationInfo(shortcutId);
+                return getConversationChannel(packageName, userId, shortcutId, conversationInfo);
             }
         }
         return null;
     }
 
     @Nullable
-    private ConversationChannel getConversationChannel(PackageData packageData, String shortcutId) {
-        ConversationInfo conversationInfo = packageData.getConversationInfo(shortcutId);
+    private ConversationChannel getConversationChannel(String packageName, int userId,
+            String shortcutId, ConversationInfo conversationInfo) {
         if (conversationInfo == null) {
             return null;
         }
-        int userId = packageData.getUserId();
-        String packageName = packageData.getPackageName();
         ShortcutInfo shortcutInfo = getShortcut(packageName, userId, shortcutId);
         if (shortcutInfo == null) {
             return null;
@@ -278,7 +286,8 @@
                     return;
                 }
                 String shortcutId = conversationInfo.getShortcutId();
-                ConversationChannel channel = getConversationChannel(packageData, shortcutId);
+                ConversationChannel channel = getConversationChannel(packageData.getPackageName(),
+                        packageData.getUserId(), shortcutId, conversationInfo);
                 if (channel == null || channel.getParentNotificationChannel() == null) {
                     return;
                 }
@@ -384,7 +393,11 @@
         ConversationInfo convToModify = getConversationInfoOrThrow(cs, conversationId);
         ConversationInfo.Builder builder = new ConversationInfo.Builder(convToModify);
         builder.addOrUpdateStatus(status);
-        cs.addOrUpdate(builder.build());
+        ConversationInfo modifiedConv = builder.build();
+        cs.addOrUpdate(modifiedConv);
+        ConversationChannel conversation = getConversationChannel(packageName, userId,
+                conversationId, modifiedConv);
+        notifyConversationsListeners(Arrays.asList(conversation));
 
         if (status.getEndTimeMillis() >= 0) {
             mStatusExpReceiver.scheduleExpiration(
@@ -1235,6 +1248,32 @@
         }
     }
 
+    /** Adds {@code listener} to be notified on conversation changes. */
+    public void addConversationsListener(
+            @NonNull PeopleService.ConversationsListener listener) {
+        synchronized (mConversationsListeners) {
+            mConversationsListeners.add(Objects.requireNonNull(listener));
+        }
+    }
+
+    // TODO(b/178792356): Trigger ConversationsListener on all-related data changes.
+    @VisibleForTesting
+    void notifyConversationsListeners(
+            @Nullable final List<ConversationChannel> changedConversations) {
+        mHandler.post(() -> {
+            try {
+                final List<PeopleService.ConversationsListener> copy;
+                synchronized (mLock) {
+                    copy = new ArrayList<>(mConversationsListeners);
+                }
+                for (PeopleService.ConversationsListener listener : copy) {
+                    listener.onConversationsUpdate(changedConversations);
+                }
+            } catch (Exception e) {
+            }
+        });
+    }
+
     /** A {@link BroadcastReceiver} that receives the intents for a specified user. */
     private class PerUserBroadcastReceiver extends BroadcastReceiver {
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index 1254df9..9109881 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -18,6 +18,7 @@
 import static android.app.AlarmManager.ELAPSED_REALTIME;
 import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
 import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE;
+import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_COMPAT;
 import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
 import static android.app.AlarmManager.FLAG_IDLE_UNTIL;
 import static android.app.AlarmManager.FLAG_STANDALONE;
@@ -25,11 +26,14 @@
 import static android.app.AlarmManager.RTC;
 import static android.app.AlarmManager.RTC_WAKEUP;
 import static android.app.AlarmManager.WINDOW_EXACT;
+import static android.app.AlarmManager.WINDOW_HEURISTIC;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
+import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
+import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod;
@@ -44,7 +48,7 @@
 import static com.android.server.alarm.AlarmManagerService.AlarmHandler.APP_STANDBY_BUCKET_CHANGED;
 import static com.android.server.alarm.AlarmManagerService.AlarmHandler.CHARGING_STATUS_CHANGED;
 import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_FOR_CANCELED;
-import static com.android.server.alarm.AlarmManagerService.Constants.ALLOW_WHILE_IDLE_WINDOW;
+import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_QUOTA;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_LAZY_BATCHING;
@@ -61,6 +65,7 @@
 import static com.android.server.alarm.Constants.TEST_CALLING_UID;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -75,20 +80,29 @@
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.never;
 
+import android.Manifest;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
+import android.app.AlarmManager;
+import android.app.AppOpsManager;
 import android.app.IActivityManager;
 import android.app.IAlarmCompleteListener;
 import android.app.IAlarmListener;
+import android.app.IAlarmManager;
 import android.app.PendingIntent;
+import android.app.compat.CompatChanges;
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.Context;
 import android.content.Intent;
+import android.content.PermissionChecker;
 import android.os.BatteryManager;
+import android.os.Bundle;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.os.PowerManager;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
@@ -102,9 +116,12 @@
 
 import com.android.dx.mockito.inline.extended.MockedVoidMethod;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.IAppOpsCallback;
+import com.android.internal.app.IAppOpsService;
 import com.android.server.AlarmManagerInternal;
 import com.android.server.AppStateTracker;
 import com.android.server.AppStateTrackerImpl;
+import com.android.server.DeviceIdleInternal;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.usage.AppStandbyInternal;
@@ -125,6 +142,7 @@
 import java.util.HashSet;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.LongConsumer;
 
 @Presubmit
 @RunWith(AndroidJUnit4.class)
@@ -134,14 +152,21 @@
     private static final int TEST_CALLING_USER = UserHandle.getUserId(TEST_CALLING_UID);
 
     private long mAppStandbyWindow;
+    private long mAllowWhileIdleWindow;
     private AlarmManagerService mService;
     private AppStandbyInternal.AppIdleStateChangeListener mAppStandbyListener;
     private AlarmManagerService.ChargingReceiver mChargingReceiver;
+    private IAppOpsCallback mIAppOpsCallback;
+    private IAlarmManager mBinder;
     @Mock
     private Context mMockContext;
     @Mock
     private IActivityManager mIActivityManager;
     @Mock
+    private IAppOpsService mIAppOpsService;
+    @Mock
+    private DeviceIdleInternal mDeviceIdleInternal;
+    @Mock
     private UsageStatsManagerInternal mUsageStatsManagerInternal;
     @Mock
     private AppStandbyInternal mAppStandbyInternal;
@@ -280,6 +305,11 @@
             // "IllegalStateException: Querying activity state off main thread is not allowed."
             // when AlarmManager calls DeviceConfig.addOnPropertiesChangedListener().
         }
+
+        @Override
+        IAppOpsService getAppOpsService() {
+            return mIAppOpsService;
+        }
     }
 
     @Before
@@ -287,15 +317,20 @@
         mMockingSession = mockitoSession()
                 .initMocks(this)
                 .spyStatic(ActivityManager.class)
+                .mockStatic(CompatChanges.class)
                 .spyStatic(DeviceConfig.class)
                 .mockStatic(LocalServices.class)
                 .spyStatic(Looper.class)
+                .mockStatic(PermissionChecker.class)
                 .mockStatic(Settings.Global.class)
                 .mockStatic(ServiceManager.class)
                 .spyStatic(UserHandle.class)
                 .strictness(Strictness.WARN)
                 .startMocking();
+
         doReturn(mIActivityManager).when(ActivityManager::getService);
+        doReturn(mDeviceIdleInternal).when(
+                () -> LocalServices.getService(DeviceIdleInternal.class));
         doReturn(mActivityManagerInternal).when(
                 () -> LocalServices.getService(ActivityManagerInternal.class));
         doReturn(mAppStateTracker).when(() -> LocalServices.getService(AppStateTracker.class));
@@ -330,6 +365,9 @@
                 () -> DeviceConfig.getProperties(
                         eq(DeviceConfig.NAMESPACE_ALARM_MANAGER), ArgumentMatchers.<String>any()));
 
+        when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(
+                mock(AppOpsManager.class));
+
         mInjector = new Injector(mMockContext);
         mService = new AlarmManagerService(mMockContext, mInjector);
         spyOn(mService);
@@ -346,6 +384,7 @@
         // Other boot phases don't matter
         mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
         mAppStandbyWindow = mService.mConstants.APP_STANDBY_WINDOW;
+        mAllowWhileIdleWindow = mService.mConstants.ALLOW_WHILE_IDLE_WINDOW;
         ArgumentCaptor<AppStandbyInternal.AppIdleStateChangeListener> captor =
                 ArgumentCaptor.forClass(AppStandbyInternal.AppIdleStateChangeListener.class);
         verify(mAppStandbyInternal).addListener(captor.capture());
@@ -358,6 +397,20 @@
                         && filter.hasAction(BatteryManager.ACTION_DISCHARGING)));
         mChargingReceiver = chargingReceiverCaptor.getValue();
 
+        ArgumentCaptor<IBinder> binderCaptor = ArgumentCaptor.forClass(IBinder.class);
+        verify(() -> ServiceManager.addService(eq(Context.ALARM_SERVICE), binderCaptor.capture(),
+                anyBoolean(), anyInt()));
+        mBinder = IAlarmManager.Stub.asInterface(binderCaptor.getValue());
+
+        ArgumentCaptor<IAppOpsCallback> appOpsCallbackCaptor = ArgumentCaptor.forClass(
+                IAppOpsCallback.class);
+        try {
+            verify(mIAppOpsService).startWatchingMode(eq(AppOpsManager.OP_SCHEDULE_EXACT_ALARM),
+                    isNull(), appOpsCallbackCaptor.capture());
+        } catch (RemoteException e) {
+            // Not expected on a mock.
+        }
+        mIAppOpsCallback = appOpsCallbackCaptor.getValue();
         setTestableQuotas();
     }
 
@@ -389,13 +442,18 @@
 
     private void setTestAlarm(int type, long triggerTime, PendingIntent operation, long interval,
             int flags, int callingUid) {
+        setTestAlarm(type, triggerTime, operation, interval, flags, callingUid, null);
+    }
+
+    private void setTestAlarm(int type, long triggerTime, PendingIntent operation, long interval,
+            int flags, int callingUid, Bundle idleOptions) {
         mService.setImpl(type, triggerTime, WINDOW_EXACT, interval, operation, null, "test", flags,
-                null, null, callingUid, TEST_CALLING_PACKAGE);
+                null, null, callingUid, TEST_CALLING_PACKAGE, idleOptions);
     }
 
     private void setTestAlarmWithListener(int type, long triggerTime, IAlarmListener listener) {
         mService.setImpl(type, triggerTime, WINDOW_EXACT, 0, null, listener, "test",
-                FLAG_STANDALONE, null, null, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
+                FLAG_STANDALONE, null, null, TEST_CALLING_UID, TEST_CALLING_PACKAGE, null);
     }
 
 
@@ -506,14 +564,27 @@
         setDeviceConfigLong(KEY_MIN_INTERVAL, 10);
         setDeviceConfigLong(KEY_MAX_INTERVAL, 15);
         setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_QUOTA, 20);
+        setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA, 25);
         setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION, 30);
         setDeviceConfigLong(KEY_LISTENER_TIMEOUT, 35);
         assertEquals(5, mService.mConstants.MIN_FUTURITY);
         assertEquals(10, mService.mConstants.MIN_INTERVAL);
         assertEquals(15, mService.mConstants.MAX_INTERVAL);
         assertEquals(20, mService.mConstants.ALLOW_WHILE_IDLE_QUOTA);
+        assertEquals(25, mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA);
         assertEquals(30, mService.mConstants.ALLOW_WHILE_IDLE_WHITELIST_DURATION);
         assertEquals(35, mService.mConstants.LISTENER_TIMEOUT);
+
+        // Test safeguards.
+        setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_QUOTA, -3);
+        assertEquals(1, mService.mConstants.ALLOW_WHILE_IDLE_QUOTA);
+        setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_QUOTA, 0);
+        assertEquals(1, mService.mConstants.ALLOW_WHILE_IDLE_QUOTA);
+
+        setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA, -8);
+        assertEquals(1, mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA);
+        setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA, 0);
+        assertEquals(1, mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA);
     }
 
     @Test
@@ -559,56 +630,45 @@
         assertEquals(mNowElapsedTest + 9, mTestTimer.getElapsed());
     }
 
-    private void testQuotasDeferralOnSet(int standbyBucket) throws Exception {
-        final int quota = mService.getQuotaForBucketLocked(standbyBucket);
-        when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
-                anyLong())).thenReturn(standbyBucket);
+    private void testQuotasDeferralOnSet(LongConsumer alarmSetter, int quota, long window)
+            throws Exception {
         final long firstTrigger = mNowElapsedTest + 10;
         for (int i = 0; i < quota; i++) {
-            setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
-                    getNewMockPendingIntent());
+            alarmSetter.accept(firstTrigger + i);
             mNowElapsedTest = mTestTimer.getElapsed();
             mTestTimer.expire();
         }
         // This one should get deferred on set
-        setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota,
-                getNewMockPendingIntent());
-        final long expectedNextTrigger = firstTrigger + mAppStandbyWindow;
+        alarmSetter.accept(firstTrigger + quota);
+        final long expectedNextTrigger = firstTrigger + window;
         assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed());
     }
 
-    private void testQuotasDeferralOnExpiration(int standbyBucket) throws Exception {
-        final int quota = mService.getQuotaForBucketLocked(standbyBucket);
-        when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
-                anyLong())).thenReturn(standbyBucket);
+    private void testQuotasDeferralOnExpiration(LongConsumer alarmSetter, int quota, long window)
+            throws Exception {
         final long firstTrigger = mNowElapsedTest + 10;
         for (int i = 0; i < quota; i++) {
-            setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
-                    getNewMockPendingIntent());
+            alarmSetter.accept(firstTrigger + i);
         }
-        // This one should get deferred after the latest alarm expires
-        setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota,
-                getNewMockPendingIntent());
+        // This one should get deferred after the latest alarm expires.
+        alarmSetter.accept(firstTrigger + quota);
         for (int i = 0; i < quota; i++) {
             mNowElapsedTest = mTestTimer.getElapsed();
             mTestTimer.expire();
         }
-        final long expectedNextTrigger = firstTrigger + mAppStandbyWindow;
+        final long expectedNextTrigger = firstTrigger + window;
         assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed());
     }
 
-    private void testQuotasNoDeferral(int standbyBucket) throws Exception {
-        final int quota = mService.getQuotaForBucketLocked(standbyBucket);
-        when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
-                anyLong())).thenReturn(standbyBucket);
+    private void testQuotasNoDeferral(LongConsumer alarmSetter, int quota, long window)
+            throws Exception {
         final long firstTrigger = mNowElapsedTest + 10;
         for (int i = 0; i < quota; i++) {
-            setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 10 + i,
-                    getNewMockPendingIntent());
+            alarmSetter.accept(firstTrigger + i);
         }
         // This delivery time maintains the quota invariant. Should not be deferred.
-        final long expectedNextTrigger = firstTrigger + mAppStandbyWindow + 5;
-        setTestAlarm(ELAPSED_REALTIME_WAKEUP, expectedNextTrigger, getNewMockPendingIntent());
+        final long expectedNextTrigger = firstTrigger + window + 5;
+        alarmSetter.accept(expectedNextTrigger);
         for (int i = 0; i < quota; i++) {
             mNowElapsedTest = mTestTimer.getElapsed();
             mTestTimer.expire();
@@ -616,64 +676,88 @@
         assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed());
     }
 
+    private void testStandbyQuotasDeferralOnSet(int standbyBucket) throws Exception {
+        final int quota = mService.getQuotaForBucketLocked(standbyBucket);
+        when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
+                anyLong())).thenReturn(standbyBucket);
+        testQuotasDeferralOnSet(trigger -> setTestAlarm(ELAPSED_REALTIME_WAKEUP, trigger,
+                getNewMockPendingIntent()), quota, mAppStandbyWindow);
+    }
+
+    private void testStandbyQuotasDeferralOnExpiration(int standbyBucket) throws Exception {
+        final int quota = mService.getQuotaForBucketLocked(standbyBucket);
+        when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
+                anyLong())).thenReturn(standbyBucket);
+        testQuotasDeferralOnExpiration(trigger -> setTestAlarm(ELAPSED_REALTIME_WAKEUP, trigger,
+                getNewMockPendingIntent()), quota, mAppStandbyWindow);
+    }
+
+    private void testStandbyQuotasNoDeferral(int standbyBucket) throws Exception {
+        final int quota = mService.getQuotaForBucketLocked(standbyBucket);
+        when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
+                anyLong())).thenReturn(standbyBucket);
+        testQuotasNoDeferral(trigger -> setTestAlarm(ELAPSED_REALTIME_WAKEUP, trigger,
+                getNewMockPendingIntent()), quota, mAppStandbyWindow);
+    }
+
     @Test
     public void testActiveQuota_deferredOnSet() throws Exception {
-        testQuotasDeferralOnSet(STANDBY_BUCKET_ACTIVE);
+        testStandbyQuotasDeferralOnSet(STANDBY_BUCKET_ACTIVE);
     }
 
     @Test
     public void testActiveQuota_deferredOnExpiration() throws Exception {
-        testQuotasDeferralOnExpiration(STANDBY_BUCKET_ACTIVE);
+        testStandbyQuotasDeferralOnExpiration(STANDBY_BUCKET_ACTIVE);
     }
 
     @Test
     public void testActiveQuota_notDeferred() throws Exception {
-        testQuotasNoDeferral(STANDBY_BUCKET_ACTIVE);
+        testStandbyQuotasNoDeferral(STANDBY_BUCKET_ACTIVE);
     }
 
     @Test
     public void testWorkingQuota_deferredOnSet() throws Exception {
-        testQuotasDeferralOnSet(STANDBY_BUCKET_WORKING_SET);
+        testStandbyQuotasDeferralOnSet(STANDBY_BUCKET_WORKING_SET);
     }
 
     @Test
     public void testWorkingQuota_deferredOnExpiration() throws Exception {
-        testQuotasDeferralOnExpiration(STANDBY_BUCKET_WORKING_SET);
+        testStandbyQuotasDeferralOnExpiration(STANDBY_BUCKET_WORKING_SET);
     }
 
     @Test
     public void testWorkingQuota_notDeferred() throws Exception {
-        testQuotasNoDeferral(STANDBY_BUCKET_WORKING_SET);
+        testStandbyQuotasNoDeferral(STANDBY_BUCKET_WORKING_SET);
     }
 
     @Test
     public void testFrequentQuota_deferredOnSet() throws Exception {
-        testQuotasDeferralOnSet(STANDBY_BUCKET_FREQUENT);
+        testStandbyQuotasDeferralOnSet(STANDBY_BUCKET_FREQUENT);
     }
 
     @Test
     public void testFrequentQuota_deferredOnExpiration() throws Exception {
-        testQuotasDeferralOnExpiration(STANDBY_BUCKET_FREQUENT);
+        testStandbyQuotasDeferralOnExpiration(STANDBY_BUCKET_FREQUENT);
     }
 
     @Test
     public void testFrequentQuota_notDeferred() throws Exception {
-        testQuotasNoDeferral(STANDBY_BUCKET_FREQUENT);
+        testStandbyQuotasNoDeferral(STANDBY_BUCKET_FREQUENT);
     }
 
     @Test
     public void testRareQuota_deferredOnSet() throws Exception {
-        testQuotasDeferralOnSet(STANDBY_BUCKET_RARE);
+        testStandbyQuotasDeferralOnSet(STANDBY_BUCKET_RARE);
     }
 
     @Test
     public void testRareQuota_deferredOnExpiration() throws Exception {
-        testQuotasDeferralOnExpiration(STANDBY_BUCKET_RARE);
+        testStandbyQuotasDeferralOnExpiration(STANDBY_BUCKET_RARE);
     }
 
     @Test
     public void testRareQuota_notDeferred() throws Exception {
-        testQuotasNoDeferral(STANDBY_BUCKET_RARE);
+        testStandbyQuotasNoDeferral(STANDBY_BUCKET_RARE);
     }
 
     @Test
@@ -731,7 +815,7 @@
     }
 
     @Test
-    public void testQuotaDowngrade() throws Exception {
+    public void testStandbyQuotaDowngrade() throws Exception {
         final int workingQuota = mService.getQuotaForBucketLocked(STANDBY_BUCKET_WORKING_SET);
         when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
                 anyLong())).thenReturn(STANDBY_BUCKET_WORKING_SET);
@@ -758,7 +842,7 @@
     }
 
     @Test
-    public void testQuotaUpgrade() throws Exception {
+    public void testStandbyQuotaUpgrade() throws Exception {
         final int frequentQuota = mService.getQuotaForBucketLocked(STANDBY_BUCKET_FREQUENT);
         when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
                 anyLong())).thenReturn(STANDBY_BUCKET_FREQUENT);
@@ -1308,7 +1392,7 @@
     public void allowWhileIdleAlarmsWhileDeviceIdle() throws Exception {
         doReturn(0).when(mService).fuzzForDuration(anyLong());
 
-        setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + ALLOW_WHILE_IDLE_WINDOW + 1000,
+        setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + mAllowWhileIdleWindow + 1000,
                 getNewMockPendingIntent());
         assertNotNull(mService.mPendingIdleUntil);
 
@@ -1323,7 +1407,7 @@
         // This one should get deferred on set.
         setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota,
                 getNewMockPendingIntent(), false);
-        final long expectedNextTrigger = firstTrigger + ALLOW_WHILE_IDLE_WINDOW;
+        final long expectedNextTrigger = firstTrigger + mAllowWhileIdleWindow;
         assertEquals("Incorrect trigger when no quota left", expectedNextTrigger,
                 mTestTimer.getElapsed());
 
@@ -1449,61 +1533,24 @@
         when(mAppStateTracker.areAlarmsRestrictedByBatterySaver(TEST_CALLING_UID,
                 TEST_CALLING_PACKAGE)).thenReturn(true);
         when(mAppStateTracker.isForceAllAppsStandbyEnabled()).thenReturn(true);
-
         final int quota = mService.mConstants.ALLOW_WHILE_IDLE_QUOTA;
-        long firstTrigger = mNowElapsedTest + 10;
-        for (int i = 0; i < quota; i++) {
-            setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
-                    getNewMockPendingIntent(), false);
-            mNowElapsedTest = mTestTimer.getElapsed();
-            mTestTimer.expire();
-        }
-        // This one should get deferred on set.
-        setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota,
-                getNewMockPendingIntent(), false);
-        long expectedNextTrigger = firstTrigger + ALLOW_WHILE_IDLE_WINDOW;
-        assertEquals("Incorrect trigger when no quota available", expectedNextTrigger,
-                mTestTimer.getElapsed());
+
+        testQuotasDeferralOnSet(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, trigger,
+                getNewMockPendingIntent(), false), quota, mAllowWhileIdleWindow);
 
         // Refresh the state
         mService.removeLocked(TEST_CALLING_UID);
         mService.mAllowWhileIdleHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
 
-        firstTrigger = mNowElapsedTest + 10;
-        for (int i = 0; i < quota; i++) {
-            setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
-                    getNewMockPendingIntent(), false);
-        }
-        // This one should get deferred after the latest alarm expires.
-        setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota,
-                getNewMockPendingIntent(), false);
-        for (int i = 0; i < quota; i++) {
-            mNowElapsedTest = mTestTimer.getElapsed();
-            mTestTimer.expire();
-        }
-        expectedNextTrigger = firstTrigger + ALLOW_WHILE_IDLE_WINDOW;
-        assertEquals("Incorrect trigger when no quota available", expectedNextTrigger,
-                mTestTimer.getElapsed());
+        testQuotasDeferralOnExpiration(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP,
+                trigger, getNewMockPendingIntent(), false), quota, mAllowWhileIdleWindow);
 
         // Refresh the state
         mService.removeLocked(TEST_CALLING_UID);
         mService.mAllowWhileIdleHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
 
-        firstTrigger = mNowElapsedTest + 10;
-        for (int i = 0; i < quota; i++) {
-            setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
-                    getNewMockPendingIntent(), false);
-        }
-        // This delivery time maintains the quota invariant. Should not be deferred.
-        expectedNextTrigger = firstTrigger + ALLOW_WHILE_IDLE_WINDOW + 5;
-        setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, expectedNextTrigger,
-                getNewMockPendingIntent(), false);
-        for (int i = 0; i < quota; i++) {
-            mNowElapsedTest = mTestTimer.getElapsed();
-            mTestTimer.expire();
-        }
-        assertEquals("Incorrect trigger when no quota available", expectedNextTrigger,
-                mTestTimer.getElapsed());
+        testQuotasNoDeferral(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, trigger,
+                getNewMockPendingIntent(), false), quota, mAllowWhileIdleWindow);
     }
 
     @Test
@@ -1545,6 +1592,259 @@
     }
 
     @Test
+    public void canScheduleExactAlarms() throws RemoteException {
+        doReturn(PermissionChecker.PERMISSION_GRANTED).when(
+                () -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
+                        Manifest.permission.SCHEDULE_EXACT_ALARM));
+        assertTrue(mBinder.canScheduleExactAlarms());
+
+        doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when(
+                () -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
+                        Manifest.permission.SCHEDULE_EXACT_ALARM));
+        assertFalse(mBinder.canScheduleExactAlarms());
+
+        doReturn(PermissionChecker.PERMISSION_SOFT_DENIED).when(
+                () -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
+                        Manifest.permission.SCHEDULE_EXACT_ALARM));
+        assertFalse(mBinder.canScheduleExactAlarms());
+    }
+
+    @Test
+    public void noPermissionCheckWhenChangeDisabled() throws RemoteException {
+        doReturn(false).when(
+                () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
+                        anyString(), any(UserHandle.class)));
+
+        // alarm clock
+        mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, 0,
+                getNewMockPendingIntent(), null, null, null,
+                mock(AlarmManager.AlarmClockInfo.class));
+
+        // exact, allow-while-idle
+        mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
+                FLAG_ALLOW_WHILE_IDLE, getNewMockPendingIntent(), null, null, null, null);
+
+        // inexact, allow-while-idle
+        mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_HEURISTIC, 0,
+                FLAG_ALLOW_WHILE_IDLE, getNewMockPendingIntent(), null, null, null, null);
+
+        verify(() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
+                Manifest.permission.SCHEDULE_EXACT_ALARM), never());
+        verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt());
+    }
+
+    @Test
+    public void exactAllowWhileIdleBinderCallWhenChangeDisabled() throws Exception {
+        doReturn(false).when(
+                () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
+                        anyString(), any(UserHandle.class)));
+
+        final PendingIntent alarmPi = getNewMockPendingIntent();
+        mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
+                FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null);
+
+        final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+        verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L),
+                eq(alarmPi), isNull(), isNull(),
+                eq(FLAG_ALLOW_WHILE_IDLE_COMPAT | FLAG_STANDALONE), isNull(), isNull(),
+                eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture());
+
+        final Bundle idleOptions = bundleCaptor.getValue();
+        final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType");
+        assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
+    }
+
+    @Test
+    public void inexactAllowWhileIdleBinderCallWhenChangeDisabled() throws Exception {
+        doReturn(false).when(
+                () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
+                        anyString(), any(UserHandle.class)));
+
+        final PendingIntent alarmPi = getNewMockPendingIntent();
+        mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_HEURISTIC, 0,
+                FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null);
+
+        final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+        verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), anyLong(), eq(0L),
+                eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE_COMPAT), isNull(),
+                isNull(), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture());
+
+        final Bundle idleOptions = bundleCaptor.getValue();
+        final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType");
+        assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
+    }
+
+    @Test
+    public void alarmClockBinderCallWhenChangeDisabled() throws Exception {
+        doReturn(false).when(
+                () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
+                        anyString(), any(UserHandle.class)));
+
+        final PendingIntent alarmPi = getNewMockPendingIntent();
+        final AlarmManager.AlarmClockInfo alarmClock = mock(AlarmManager.AlarmClockInfo.class);
+        mBinder.set(TEST_CALLING_PACKAGE, RTC_WAKEUP, 1234, WINDOW_EXACT, 0, 0,
+                alarmPi, null, null, null, alarmClock);
+
+        verify(mService).setImpl(eq(RTC_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L),
+                eq(alarmPi), isNull(), isNull(), eq(FLAG_STANDALONE | FLAG_WAKE_FROM_IDLE),
+                isNull(), eq(alarmClock), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), isNull());
+    }
+
+    @Test
+    public void alarmClockBinderCall() throws RemoteException {
+        doReturn(true).when(
+                () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
+                        anyString(), any(UserHandle.class)));
+
+        doReturn(PermissionChecker.PERMISSION_GRANTED).when(
+                () -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
+                        Manifest.permission.SCHEDULE_EXACT_ALARM));
+
+        final PendingIntent alarmPi = getNewMockPendingIntent();
+        final AlarmManager.AlarmClockInfo alarmClock = mock(AlarmManager.AlarmClockInfo.class);
+        mBinder.set(TEST_CALLING_PACKAGE, RTC_WAKEUP, 1234, WINDOW_EXACT, 0, 0,
+                alarmPi, null, null, null, alarmClock);
+
+        // Correct permission checks are invoked.
+        verify(() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
+                Manifest.permission.SCHEDULE_EXACT_ALARM));
+        verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt());
+
+        final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+        verify(mService).setImpl(eq(RTC_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L),
+                eq(alarmPi), isNull(), isNull(), eq(FLAG_STANDALONE | FLAG_WAKE_FROM_IDLE),
+                isNull(), eq(alarmClock), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE),
+                bundleCaptor.capture());
+
+        final Bundle idleOptions = bundleCaptor.getValue();
+        final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType");
+        assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
+    }
+
+    @Test
+    public void exactAllowWhileIdleBinderCallWithPermission() throws RemoteException {
+        doReturn(true).when(
+                () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
+                        anyString(), any(UserHandle.class)));
+
+        // Permission check is granted by default by the mock.
+        final PendingIntent alarmPi = getNewMockPendingIntent();
+        mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
+                FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null);
+
+        verify(() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
+                Manifest.permission.SCHEDULE_EXACT_ALARM));
+        verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt());
+
+        final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+        verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L),
+                eq(alarmPi), isNull(), isNull(),
+                eq(FLAG_ALLOW_WHILE_IDLE | FLAG_STANDALONE), isNull(), isNull(),
+                eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture());
+
+        final Bundle idleOptions = bundleCaptor.getValue();
+        final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType");
+        assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
+    }
+
+    @Test
+    public void exactAllowWhileIdleBinderCallWithAllowlist() throws RemoteException {
+        doReturn(true).when(
+                () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
+                        anyString(), any(UserHandle.class)));
+        // If permission is denied, only then allowlist will be checked.
+        doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when(
+                () -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
+                        Manifest.permission.SCHEDULE_EXACT_ALARM));
+        when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
+
+        final PendingIntent alarmPi = getNewMockPendingIntent();
+        mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
+                FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null);
+
+        verify(() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
+                Manifest.permission.SCHEDULE_EXACT_ALARM));
+        verify(mDeviceIdleInternal).isAppOnWhitelist(UserHandle.getAppId(Process.myUid()));
+
+        final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+        verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L),
+                eq(alarmPi), isNull(), isNull(),
+                eq(FLAG_ALLOW_WHILE_IDLE_COMPAT | FLAG_STANDALONE), isNull(), isNull(),
+                eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture());
+
+        final Bundle idleOptions = bundleCaptor.getValue();
+        final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType");
+        assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
+    }
+
+    @Test
+    public void inexactAllowWhileIdleBinderCallWithAllowlist() throws RemoteException {
+        doReturn(true).when(
+                () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
+                        anyString(), any(UserHandle.class)));
+
+        when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
+        final PendingIntent alarmPi = getNewMockPendingIntent();
+        mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 4321, WINDOW_HEURISTIC, 0,
+                FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null);
+
+        verify(() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
+                Manifest.permission.SCHEDULE_EXACT_ALARM), never());
+        verify(mDeviceIdleInternal).isAppOnWhitelist(UserHandle.getAppId(Process.myUid()));
+
+        final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+        verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(4321L), anyLong(), eq(0L),
+                eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE_COMPAT), isNull(),
+                isNull(), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture());
+
+        final Bundle idleOptions = bundleCaptor.getValue();
+        final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType");
+        assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
+    }
+
+    @Test
+    public void inexactAllowWhileIdleBinderCallWithoutAllowlist() throws RemoteException {
+        doReturn(true).when(
+                () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
+                        anyString(), any(UserHandle.class)));
+
+        when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(false);
+        final PendingIntent alarmPi = getNewMockPendingIntent();
+        mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 4321, WINDOW_HEURISTIC, 0,
+                FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null);
+
+        verify(() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
+                Manifest.permission.SCHEDULE_EXACT_ALARM), never());
+        verify(mDeviceIdleInternal).isAppOnWhitelist(UserHandle.getAppId(Process.myUid()));
+
+        final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+        verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(4321L), anyLong(), eq(0L),
+                eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE_COMPAT), isNull(),
+                isNull(), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture());
+
+        final Bundle idleOptions = bundleCaptor.getValue();
+        final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType");
+        assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED, type);
+    }
+
+    @Test
+    public void idleOptionsSentOnExpiration() throws Exception {
+        final long triggerTime = mNowElapsedTest + 5000;
+        final PendingIntent alarmPi = getNewMockPendingIntent();
+        final Bundle idleOptions = new Bundle();
+        idleOptions.putChar("TEST_CHAR_KEY", 'x');
+        idleOptions.putInt("TEST_INT_KEY", 53);
+        setTestAlarm(ELAPSED_REALTIME_WAKEUP, triggerTime, alarmPi, 0, 0, TEST_CALLING_UID,
+                idleOptions);
+
+        mNowElapsedTest = mTestTimer.getElapsed();
+        mTestTimer.expire();
+
+        verify(alarmPi).send(eq(mMockContext), eq(0), any(Intent.class),
+                any(), any(Handler.class), isNull(), eq(idleOptions));
+    }
+
+    @Test
     public void alarmStoreMigration() {
         setDeviceConfigBoolean(KEY_LAZY_BATCHING, false);
         final int numAlarms = 10;
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java
index 42fa3d4..12894ae 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java
@@ -69,12 +69,12 @@
                 mock(PendingIntent.class));
         return new Alarm(ELAPSED_REALTIME_WAKEUP, whenElapsed, whenElapsed, 0, 0,
                 mock(PendingIntent.class), null, null, null, 0, info, TEST_CALLING_UID,
-                TEST_CALLING_PACKAGE);
+                TEST_CALLING_PACKAGE, null);
     }
 
     private static Alarm createAlarm(int type, long whenElapsed, long windowLength, int flags) {
         return new Alarm(type, whenElapsed, whenElapsed, windowLength, 0, mock(PendingIntent.class),
-                null, null, null, flags, null, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
+                null, null, null, flags, null, TEST_CALLING_UID, TEST_CALLING_PACKAGE, null);
     }
 
     private void addAlarmsToStore(Alarm... alarms) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmTest.java
index e80f065..b64528c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmTest.java
@@ -17,10 +17,17 @@
 package com.android.server.alarm;
 
 import static android.app.AlarmManager.ELAPSED_REALTIME;
+import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE;
+import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_COMPAT;
+import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
+import static android.app.AlarmManager.FLAG_STANDALONE;
+import static android.app.AlarmManager.FLAG_WAKE_FROM_IDLE;
+import static android.app.AlarmManager.RTC_WAKEUP;
 
 import static com.android.server.alarm.Alarm.APP_STANDBY_POLICY_INDEX;
 import static com.android.server.alarm.Alarm.NUM_POLICIES;
 import static com.android.server.alarm.Alarm.REQUESTER_POLICY_INDEX;
+import static com.android.server.alarm.AlarmManagerService.isExemptFromAppStandby;
 import static com.android.server.alarm.Constants.TEST_CALLING_PACKAGE;
 import static com.android.server.alarm.Constants.TEST_CALLING_UID;
 
@@ -28,7 +35,9 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
+import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.platform.test.annotations.Presubmit;
 
@@ -43,15 +52,29 @@
 @RunWith(AndroidJUnit4.class)
 public class AlarmTest {
 
-    private Alarm createDefaultAlarm(long requestedElapsed, long windowLength) {
+    private Alarm createDefaultAlarm(long requestedElapsed, long windowLength, int flags) {
         return new Alarm(ELAPSED_REALTIME, 0, requestedElapsed, windowLength, 0,
-                mock(PendingIntent.class), null, null, null, 0, null, TEST_CALLING_UID,
-                TEST_CALLING_PACKAGE);
+                createAlarmSender(), null, null, null, flags, null, TEST_CALLING_UID,
+                TEST_CALLING_PACKAGE, null);
+    }
+
+    private Alarm createAlarmClock(long requestedRtc) {
+        final AlarmManager.AlarmClockInfo info = mock(AlarmManager.AlarmClockInfo.class);
+        return new Alarm(RTC_WAKEUP, requestedRtc, requestedRtc, 0, 0, createAlarmSender(),
+                null, null, null, FLAG_WAKE_FROM_IDLE | FLAG_STANDALONE, info, TEST_CALLING_UID,
+                TEST_CALLING_PACKAGE, null);
+    }
+
+    private PendingIntent createAlarmSender() {
+        final PendingIntent alarmPi = mock(PendingIntent.class);
+        when(alarmPi.getCreatorPackage()).thenReturn(TEST_CALLING_PACKAGE);
+        when(alarmPi.getCreatorUid()).thenReturn(TEST_CALLING_UID);
+        return alarmPi;
     }
 
     @Test
     public void initSetsOnlyRequesterPolicy() {
-        final Alarm a = createDefaultAlarm(4567, 2);
+        final Alarm a = createDefaultAlarm(4567, 2, 0);
 
         for (int i = 0; i < NUM_POLICIES; i++) {
             if (i == REQUESTER_POLICY_INDEX) {
@@ -86,7 +109,7 @@
 
     @Test
     public void whenElapsed() {
-        final Alarm a = createDefaultAlarm(0, 0);
+        final Alarm a = createDefaultAlarm(0, 0, 0);
 
         final long[][] uniqueData = generatePolicyTestMatrix(NUM_POLICIES);
         for (int i = 0; i < NUM_POLICIES; i++) {
@@ -104,7 +127,7 @@
 
     @Test
     public void maxWhenElapsed() {
-        final Alarm a = createDefaultAlarm(10, 12);
+        final Alarm a = createDefaultAlarm(10, 12, 0);
         assertEquals(22, a.getMaxWhenElapsed());
 
         a.setPolicyElapsed(REQUESTER_POLICY_INDEX, 15);
@@ -128,7 +151,7 @@
 
     @Test
     public void setPolicyElapsedExact() {
-        final Alarm exactAlarm = createDefaultAlarm(10, 0);
+        final Alarm exactAlarm = createDefaultAlarm(10, 0, 0);
 
         assertTrue(exactAlarm.setPolicyElapsed(REQUESTER_POLICY_INDEX, 4));
         assertTrue(exactAlarm.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, 10));
@@ -143,7 +166,7 @@
 
     @Test
     public void setPolicyElapsedInexact() {
-        final Alarm inexactAlarm = createDefaultAlarm(10, 5);
+        final Alarm inexactAlarm = createDefaultAlarm(10, 5, 0);
 
         assertTrue(inexactAlarm.setPolicyElapsed(REQUESTER_POLICY_INDEX, 4));
         assertTrue(inexactAlarm.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, 10));
@@ -154,4 +177,20 @@
 
         assertFalse(inexactAlarm.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, 8));
     }
+
+    @Test
+    public void isExemptFromStandby() {
+        final long anything = 35412;    // Arbitrary number, doesn't matter for this test.
+
+        assertFalse("Basic alarm exempt", isExemptFromAppStandby(
+                createDefaultAlarm(anything, anything, 0)));
+        assertFalse("FLAG_ALLOW_WHILE_IDLE_COMPAT exempt", isExemptFromAppStandby(
+                createDefaultAlarm(anything, anything, FLAG_ALLOW_WHILE_IDLE_COMPAT)));
+
+        assertTrue("ALLOW_WHILE_IDLE not exempt", isExemptFromAppStandby(
+                createDefaultAlarm(anything, anything, FLAG_ALLOW_WHILE_IDLE)));
+        assertTrue("ALLOW_WHILE_IDLE_UNRESTRICTED not exempt", isExemptFromAppStandby(
+                createDefaultAlarm(anything, anything, FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED)));
+        assertTrue("Alarm clock not exempt", isExemptFromAppStandby(createAlarmClock(anything)));
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/BackgroundRestrictedAlarmsTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/BackgroundRestrictedAlarmsTest.java
index 5bb6a42..0e795a9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/BackgroundRestrictedAlarmsTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/BackgroundRestrictedAlarmsTest.java
@@ -45,7 +45,7 @@
         }
         uidAlarms.add(new Alarm(
                 removeIt ? RTC : RTC_WAKEUP,
-                0, 0, 0, 0, null, null, null, null, 0, null, uid, name));
+                0, 0, 0, 0, null, null, null, null, 0, null, uid, name, null));
         return all;
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/OWNERS b/services/tests/mockingservicestests/src/com/android/server/pm/OWNERS
index d825dfd..46b797b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/OWNERS
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/OWNERS
@@ -1 +1,3 @@
 include /services/core/java/com/android/server/pm/OWNERS
+
+per-file StagingManagerTest.java = dariofreni@google.com, ioffe@google.com, olilan@google.com
diff --git a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
index a946534..8d54ead 100644
--- a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
@@ -16,6 +16,12 @@
 
 package com.android.server.am;
 
+import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL;
+import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_CPU;
+import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_DISPLAY;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 
 import android.content.Context;
@@ -27,6 +33,7 @@
 import android.hardware.power.stats.PowerEntity;
 import android.hardware.power.stats.StateResidencyResult;
 import android.power.PowerStatsInternal;
+import android.util.IntArray;
 import android.util.SparseArray;
 
 import androidx.test.InstrumentationRegistry;
@@ -34,7 +41,9 @@
 import com.android.internal.os.BatteryStatsImpl;
 
 import org.junit.Before;
+import org.junit.Test;
 
+import java.util.Arrays;
 import java.util.concurrent.CompletableFuture;
 
 /**
@@ -58,6 +67,69 @@
                 mBatteryStatsImpl);
     }
 
+    @Test
+    public void testTargetedEnergyConsumerQuerying() {
+        final int numCpuClusters = 4;
+        final int numOther = 3;
+
+        final IntArray tempAllIds = new IntArray();
+        // Add some energy consumers used by BatteryExternalStatsWorker.
+        final int displayId = mPowerStatsInternal.addEnergyConsumer(EnergyConsumerType.DISPLAY, 0,
+                "display");
+        tempAllIds.add(displayId);
+        mPowerStatsInternal.incrementEnergyConsumption(displayId, 12345);
+
+        final int[] cpuClusterIds = new int[numCpuClusters];
+        for (int i = 0; i < numCpuClusters; i++) {
+            cpuClusterIds[i] = mPowerStatsInternal.addEnergyConsumer(
+                    EnergyConsumerType.CPU_CLUSTER, i, "cpu_cluster" + i);
+            tempAllIds.add(cpuClusterIds[i]);
+            mPowerStatsInternal.incrementEnergyConsumption(cpuClusterIds[i], 1111 + i);
+        }
+        Arrays.sort(cpuClusterIds);
+
+        final int[] otherIds = new int[numOther];
+        for (int i = 0; i < numOther; i++) {
+            otherIds[i] = mPowerStatsInternal.addEnergyConsumer(
+                    EnergyConsumerType.OTHER, i, "other" + i);
+            tempAllIds.add(otherIds[i]);
+            mPowerStatsInternal.incrementEnergyConsumption(otherIds[i], 3000 + i);
+        }
+        Arrays.sort(otherIds);
+
+        final int[] allIds = tempAllIds.toArray();
+        Arrays.sort(allIds);
+
+        // Inform BESW that PowerStatsInternal is ready to query
+        mBatteryExternalStatsWorker.systemServicesReady();
+
+        final EnergyConsumerResult[] displayResults =
+                mBatteryExternalStatsWorker.getMeasuredEnergyLocked(UPDATE_DISPLAY).getNow(null);
+        // Results should only have the display energy consumer
+        assertEquals(1, displayResults.length);
+        assertEquals(displayId, displayResults[0].id);
+
+        final EnergyConsumerResult[] cpuResults =
+                mBatteryExternalStatsWorker.getMeasuredEnergyLocked(UPDATE_CPU).getNow(null);
+        // Results should only have the cpu cluster energy consumers
+        final int[] receivedCpuIds = new int[cpuResults.length];
+        for (int i = 0; i < cpuResults.length; i++) {
+            receivedCpuIds[i] = cpuResults[i].id;
+        }
+        Arrays.sort(receivedCpuIds);
+        assertArrayEquals(cpuClusterIds, receivedCpuIds);
+
+        final EnergyConsumerResult[] allResults =
+                mBatteryExternalStatsWorker.getMeasuredEnergyLocked(UPDATE_ALL).getNow(null);
+        // All energy consumer results should be available
+        final int[] receivedAllIds = new int[allResults.length];
+        for (int i = 0; i < allResults.length; i++) {
+            receivedAllIds[i] = allResults[i].id;
+        }
+        Arrays.sort(receivedAllIds);
+        assertArrayEquals(allIds, receivedAllIds);
+    }
+
     public class TestInjector extends BatteryExternalStatsWorker.Injector {
         public TestInjector(Context context) {
             super(context);
@@ -79,9 +151,8 @@
     }
 
     public class TestPowerStatsInternal extends PowerStatsInternal {
-        private final SparseArray<EnergyConsumer> mEnergyConsumers = new SparseArray<>();
-        private final SparseArray<EnergyConsumerResult> mEnergyConsumerResults =
-                new SparseArray<>();
+        private final SparseArray<EnergyConsumer> mEnergyConsumers = new SparseArray();
+        private final SparseArray<EnergyConsumerResult> mEnergyConsumerResults = new SparseArray();
         private final int mTimeSinceBoot = 0;
 
         @Override
@@ -98,10 +169,19 @@
         public CompletableFuture<EnergyConsumerResult[]> getEnergyConsumedAsync(
                 int[] energyConsumerIds) {
             final CompletableFuture<EnergyConsumerResult[]> future = new CompletableFuture();
-            final int size = mEnergyConsumerResults.size();
-            final EnergyConsumerResult[] results = new EnergyConsumerResult[size];
-            for (int i = 0; i < size; i++) {
-                results[i] = mEnergyConsumerResults.valueAt(i);
+            final EnergyConsumerResult[] results;
+            final int length = energyConsumerIds.length;
+            if (length == 0) {
+                final int size = mEnergyConsumerResults.size();
+                results = new EnergyConsumerResult[size];
+                for (int i = 0; i < size; i++) {
+                    results[i] = mEnergyConsumerResults.valueAt(i);
+                }
+            } else {
+                results = new EnergyConsumerResult[length];
+                for (int i = 0; i < length; i++) {
+                    results[i] = mEnergyConsumerResults.get(energyConsumerIds[i]);
+                }
             }
             future.complete(results);
             return future;
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 5bb65ab..6add8d1 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -3993,6 +3993,30 @@
     }
 
     @Test
+    public void testGetSetNetworkSlicing() throws Exception {
+        assertExpectException(SecurityException.class, null,
+                () -> dpm.setNetworkSlicingEnabled(false));
+
+        assertExpectException(SecurityException.class, null,
+                () -> dpm.isNetworkSlicingEnabled());
+
+        assertExpectException(SecurityException.class, null,
+                () -> dpm.isNetworkSlicingEnabledForUser(UserHandle.of(CALLER_USER_HANDLE)));
+
+        mContext.callerPermissions.add(permission.READ_NETWORK_DEVICE_CONFIG);
+        mContext.callerPermissions.add(permission.INTERACT_ACROSS_USERS_FULL);
+        try {
+            dpm.isNetworkSlicingEnabledForUser(UserHandle.of(CALLER_USER_HANDLE));
+        } catch (SecurityException se) {
+            fail("Threw SecurityException with right permission");
+        }
+
+        setupProfileOwner();
+        dpm.setNetworkSlicingEnabled(false);
+        assertThat(dpm.isNetworkSlicingEnabled()).isFalse();
+    }
+
+    @Test
     public void testSetSystemSettingFailWithNonWhitelistedSettings() throws Exception {
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
index fcbd897..1958cb0 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
@@ -58,6 +58,7 @@
     private int mMyPhysicalAddress = 0;
     private HdmiPortInfo[] mHdmiPortInfo = null;
     private HdmiCecController.HdmiCecCallback mCallback = null;
+    private int mCecVersion = HdmiControlManager.HDMI_CEC_VERSION_2_0;
 
     @Override
     public String nativeInit() {
@@ -96,7 +97,7 @@
 
     @Override
     public int nativeGetVersion() {
-        return HdmiControlManager.HDMI_CEC_VERSION_2_0;
+        return mCecVersion;
     }
 
     @Override
@@ -132,6 +133,10 @@
         mPortConnectionStatus.put(port, connected);
     }
 
+    public void setCecVersion(@HdmiControlManager.HdmiCecVersion int cecVersion) {
+        mCecVersion = cecVersion;
+    }
+
     public void onCecMessage(HdmiCecMessage hdmiCecMessage) {
         if (mCallback == null) {
             return;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index be584d7..462f3e3 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -49,6 +49,7 @@
 import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -622,6 +623,45 @@
         assertEquals(runnerUid, Binder.getCallingWorkSourceUid());
     }
 
+    @Ignore("b/180499471")
+    @Test
+    public void initCecVersion_limitToMinimumSupportedVersion() {
+        mHdmiControlService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+                HdmiControlManager.HDMI_CEC_VERSION_2_0);
+        mNativeWrapper.setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
+
+        mHdmiControlService.initService();
+        assertThat(mHdmiControlService.getCecVersion()).isEqualTo(
+                HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
+    }
+
+    @Ignore("b/180499471")
+    @Test
+    public void initCecVersion_limitToAtLeast1_4() {
+        mHdmiControlService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+                HdmiControlManager.HDMI_CEC_VERSION_2_0);
+        mNativeWrapper.setCecVersion(0x0);
+
+        mHdmiControlService.initService();
+        assertThat(mHdmiControlService.getCecVersion()).isEqualTo(
+                HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
+    }
+
+    @Ignore("b/180499471")
+    @Test
+    public void initCecVersion_useHighestMatchingVersion() {
+        mHdmiControlService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+                HdmiControlManager.HDMI_CEC_VERSION_2_0);
+        mNativeWrapper.setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_2_0);
+
+        mHdmiControlService.initService();
+        assertThat(mHdmiControlService.getCecVersion()).isEqualTo(
+                HdmiControlManager.HDMI_CEC_VERSION_2_0);
+    }
+
     private static class VolumeControlFeatureCallback extends
             IHdmiCecVolumeControlFeatureListener.Stub {
         boolean mCallbackReceived = false;
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index 1f66c7c..67d6929 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -339,11 +339,11 @@
         mService.setLockCredential(nonePassword(), newPattern("123654"), PRIMARY_USER_ID);
 
         // Verify fingerprint is removed
-        verify(mFingerprintManager).remove(any(), eq(PRIMARY_USER_ID), any());
-        verify(mFaceManager).remove(any(), eq(PRIMARY_USER_ID), any());
+        verify(mFingerprintManager).removeAll(eq(PRIMARY_USER_ID), any());
+        verify(mFaceManager).removeAll(eq(PRIMARY_USER_ID), any());
 
-        verify(mFingerprintManager).remove(any(), eq(MANAGED_PROFILE_USER_ID), any());
-        verify(mFaceManager).remove(any(), eq(MANAGED_PROFILE_USER_ID), any());
+        verify(mFingerprintManager).removeAll(eq(MANAGED_PROFILE_USER_ID), any());
+        verify(mFaceManager).removeAll(eq(MANAGED_PROFILE_USER_ID), any());
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java b/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java
index a112b14..ecff409 100644
--- a/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java
@@ -16,58 +16,104 @@
 
 package com.android.server.people;
 
+import static android.app.people.ConversationStatus.ACTIVITY_GAME;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.people.ConversationChannel;
+import android.app.people.ConversationStatus;
+import android.app.people.IConversationListener;
+import android.app.people.IPeopleManager;
+import android.app.people.PeopleManager;
 import android.app.prediction.AppPredictionContext;
 import android.app.prediction.AppPredictionSessionId;
 import android.app.prediction.AppTarget;
 import android.app.prediction.IPredictionCallback;
 import android.content.Context;
 import android.content.pm.ParceledListSlice;
+import android.content.pm.ShortcutInfo;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.RemoteException;
+import android.os.test.TestLooper;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.testing.TestableLooper;
+
+import androidx.test.InstrumentationRegistry;
 
 import com.android.server.LocalServices;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.function.Consumer;
 
-@RunWith(JUnit4.class)
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public final class PeopleServiceTest {
-
     private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share";
     private static final int APP_PREDICTION_TARGET_COUNT = 4;
     private static final String TEST_PACKAGE_NAME = "com.example";
     private static final int USER_ID = 0;
+    private static final String CONVERSATION_ID_1 = "12";
+    private static final String CONVERSATION_ID_2 = "123";
 
     private PeopleServiceInternal mServiceInternal;
     private PeopleService.LocalService mLocalService;
     private AppPredictionSessionId mSessionId;
     private AppPredictionContext mPredictionContext;
 
-    @Mock private Context mContext;
-    @Mock private IPredictionCallback mCallback;
+    @Mock
+    private Context mMockContext;
+
+    @Rule
+    public final TestableContext mContext =
+            new TestableContext(InstrumentationRegistry.getContext(), null);
+
+    protected TestableContext getContext() {
+        return mContext;
+    }
+
+    @Mock
+    private IPredictionCallback mCallback;
+    private TestableLooper mTestableLooper;
+    private final TestLooper mTestLooper = new TestLooper();
+
+    private TestablePeopleService mPeopleService;
+    private IPeopleManager mIPeopleManager;
+    private PeopleManager mPeopleManager;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        when(mContext.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
+        mPeopleService = new TestablePeopleService(mContext);
+        mTestableLooper = TestableLooper.get(this);
+        mIPeopleManager = ((IPeopleManager) mPeopleService.mService);
+        mPeopleManager = new PeopleManager(mContext, mIPeopleManager);
+        when(mMockContext.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
         when(mCallback.asBinder()).thenReturn(new Binder());
-
         PeopleService service = new PeopleService(mContext);
         service.onStart(/* isForTesting= */ true);
 
@@ -75,7 +121,7 @@
         mLocalService = (PeopleService.LocalService) mServiceInternal;
 
         mSessionId = new AppPredictionSessionId("abc", USER_ID);
-        mPredictionContext = new AppPredictionContext.Builder(mContext)
+        mPredictionContext = new AppPredictionContext.Builder(mMockContext)
                 .setUiSurface(APP_PREDICTION_SHARE_UI_SURFACE)
                 .setPredictedTargetCount(APP_PREDICTION_TARGET_COUNT)
                 .setExtras(new Bundle())
@@ -111,4 +157,134 @@
 
         mServiceInternal.onDestroyPredictionSession(mSessionId);
     }
+
+    @Test
+    public void testRegisterConversationListener() throws Exception {
+        assertEquals(0,
+                mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+
+        mIPeopleManager.registerConversationListener(TEST_PACKAGE_NAME, 0, CONVERSATION_ID_1,
+                new TestableConversationListener());
+        mTestableLooper.processAllMessages();
+        assertEquals(1,
+                mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+
+        mIPeopleManager.registerConversationListener(TEST_PACKAGE_NAME, 0, CONVERSATION_ID_1,
+                new TestableConversationListener());
+        mTestableLooper.processAllMessages();
+        assertEquals(2,
+                mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+
+        mIPeopleManager.registerConversationListener(TEST_PACKAGE_NAME, 0, CONVERSATION_ID_2,
+                new TestableConversationListener());
+        mTestableLooper.processAllMessages();
+        assertEquals(3,
+                mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+    }
+
+    @Test
+    public void testUnregisterConversationListener() throws Exception {
+        TestableConversationListener listener1 = new TestableConversationListener();
+        mIPeopleManager.registerConversationListener(TEST_PACKAGE_NAME, 0, CONVERSATION_ID_1,
+                listener1);
+        TestableConversationListener listener2 = new TestableConversationListener();
+        mIPeopleManager.registerConversationListener(TEST_PACKAGE_NAME, 0, CONVERSATION_ID_1,
+                listener2);
+        TestableConversationListener listener3 = new TestableConversationListener();
+        mIPeopleManager.registerConversationListener(TEST_PACKAGE_NAME, 0, CONVERSATION_ID_2,
+                listener3);
+        mTestableLooper.processAllMessages();
+        assertEquals(3,
+                mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+
+        mIPeopleManager.unregisterConversationListener(
+                listener2);
+        assertEquals(2,
+                mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+        mIPeopleManager.unregisterConversationListener(
+                listener1);
+        assertEquals(1,
+                mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+        mIPeopleManager.unregisterConversationListener(
+                listener3);
+        assertEquals(0,
+                mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+    }
+
+    @Test
+    public void testOnlyTriggersConversationListenersForRegisteredConversation() {
+        PeopleManager.ConversationListener listenerForConversation1 = mock(
+                PeopleManager.ConversationListener.class);
+        registerListener(CONVERSATION_ID_1, listenerForConversation1);
+        PeopleManager.ConversationListener secondListenerForConversation1 = mock(
+                PeopleManager.ConversationListener.class);
+        registerListener(CONVERSATION_ID_1, secondListenerForConversation1);
+        PeopleManager.ConversationListener listenerForConversation2 = mock(
+                PeopleManager.ConversationListener.class);
+        registerListener(CONVERSATION_ID_2, listenerForConversation2);
+        assertEquals(3,
+                mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+
+        // Update conversation with two listeners.
+        ConversationStatus status = new ConversationStatus.Builder(CONVERSATION_ID_1,
+                ACTIVITY_GAME).build();
+        mPeopleService.mConversationListenerHelper.onConversationsUpdate(
+                Arrays.asList(getConversation(CONVERSATION_ID_1, status)));
+        mTestLooper.dispatchAll();
+
+        // Never update listeners for other conversations.
+        verify(listenerForConversation2, never()).onConversationUpdate(any());
+        // Should update both listeners for the conversation.
+        ArgumentCaptor<ConversationChannel> capturedConversation = ArgumentCaptor.forClass(
+                ConversationChannel.class);
+        verify(listenerForConversation1, times(1)).onConversationUpdate(
+                capturedConversation.capture());
+        ConversationChannel conversationChannel = capturedConversation.getValue();
+        verify(secondListenerForConversation1, times(1)).onConversationUpdate(
+                eq(conversationChannel));
+        assertEquals(conversationChannel.getShortcutInfo().getId(), CONVERSATION_ID_1);
+        assertThat(conversationChannel.getStatuses()).containsExactly(status);
+    }
+
+    private void registerListener(String conversationId,
+            PeopleManager.ConversationListener listener) {
+        mPeopleManager.registerConversationListener(mContext.getPackageName(), mContext.getUserId(),
+                conversationId, listener,
+                mTestLooper.getNewExecutor());
+    }
+
+    private ConversationChannel getConversation(String shortcutId, ConversationStatus status) {
+        ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(mContext,
+                shortcutId).setLongLabel(
+                "name").build();
+        NotificationChannel notificationChannel = new NotificationChannel("123",
+                "channel",
+                NotificationManager.IMPORTANCE_DEFAULT);
+        return new ConversationChannel(shortcutInfo, 0,
+                notificationChannel, null,
+                123L, false, false, Arrays.asList(status));
+    }
+
+    private class TestableConversationListener extends IConversationListener.Stub {
+        @Override
+        public void onConversationUpdate(ConversationChannel conversation) {
+        }
+    }
+
+    // Use a Testable subclass so we can simulate calls from the system without failing.
+    private static class TestablePeopleService extends PeopleService {
+        TestablePeopleService(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void onStart() {
+            super.onStart(true);
+        }
+
+        @Override
+        protected void enforceSystemRootOrSystemUI(Context context, String message) {
+            return;
+        }
+    }
 }
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 50d9f61..7709edb 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
@@ -79,6 +79,7 @@
 import android.os.Looper;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.test.TestLooper;
 import android.provider.ContactsContract;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.StatusBarNotification;
@@ -91,8 +92,11 @@
 import com.android.internal.content.PackageMonitor;
 import com.android.server.LocalServices;
 import com.android.server.notification.NotificationManagerInternal;
+import com.android.server.people.PeopleService;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 
+import com.google.common.collect.Iterables;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -123,6 +127,7 @@
     private static final String TEST_PKG_NAME = "pkg";
     private static final String TEST_CLASS_NAME = "class";
     private static final String TEST_SHORTCUT_ID = "sc";
+    private static final String TEST_SHORTCUT_ID_2 = "sc2";
     private static final int TEST_PKG_UID = 35;
     private static final String CONTACT_URI = "content://com.android.contacts/contacts/lookup/123";
     private static final String PHONE_NUMBER = "+1234567890";
@@ -157,6 +162,7 @@
     private ShortcutChangeCallback mShortcutChangeCallback;
     private ShortcutInfo mShortcutInfo;
     private TestInjector mInjector;
+    private TestLooper mLooper;
 
     @Before
     public void setUp() throws PackageManager.NameNotFoundException {
@@ -237,7 +243,8 @@
         mCancellationSignal = new CancellationSignal();
 
         mInjector = new TestInjector();
-        mDataManager = new DataManager(mContext, mInjector);
+        mLooper = new TestLooper();
+        mDataManager = new DataManager(mContext, mInjector, mLooper.getLooper());
         mDataManager.initialize();
 
         when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(),
@@ -515,6 +522,84 @@
     }
 
     @Test
+    public void testAddConversationsListener() throws Exception {
+        mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+        ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+                buildPerson());
+        mDataManager.addOrUpdateConversationInfo(shortcut);
+        ConversationChannel conversationChannel = mDataManager.getConversation(TEST_PKG_NAME,
+                USER_ID_PRIMARY,
+                TEST_SHORTCUT_ID);
+
+        PeopleService.ConversationsListener listener = mock(
+                PeopleService.ConversationsListener.class);
+        mDataManager.addConversationsListener(listener);
+
+        List<ConversationChannel> changedConversations = Arrays.asList(conversationChannel);
+        verify(listener, times(0)).onConversationsUpdate(eq(changedConversations));
+        mDataManager.notifyConversationsListeners(changedConversations);
+        mLooper.dispatchAll();
+
+        verify(listener, times(1)).onConversationsUpdate(eq(changedConversations));
+    }
+
+    @Test
+    public void testAddConversationListenersNotifiesMultipleConversations() throws Exception {
+        mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+        ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+                buildPerson());
+        mDataManager.addOrUpdateConversationInfo(shortcut);
+        ConversationChannel conversationChannel = mDataManager.getConversation(TEST_PKG_NAME,
+                USER_ID_PRIMARY,
+                TEST_SHORTCUT_ID);
+        ShortcutInfo shortcut2 = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY,
+                TEST_SHORTCUT_ID_2,
+                buildPerson());
+        mDataManager.addOrUpdateConversationInfo(shortcut2);
+        ConversationChannel conversationChannel2 = mDataManager.getConversation(TEST_PKG_NAME,
+                USER_ID_PRIMARY,
+                TEST_SHORTCUT_ID_2);
+        PeopleService.ConversationsListener listener = mock(
+                PeopleService.ConversationsListener.class);
+        mDataManager.addConversationsListener(listener);
+
+        List<ConversationChannel> changedConversations = Arrays.asList(conversationChannel,
+                conversationChannel2);
+        verify(listener, times(0)).onConversationsUpdate(eq(changedConversations));
+        mDataManager.notifyConversationsListeners(changedConversations);
+        mLooper.dispatchAll();
+
+        verify(listener, times(1)).onConversationsUpdate(eq(changedConversations));
+        ArgumentCaptor<List<ConversationChannel>> capturedConversation = ArgumentCaptor.forClass(
+                List.class);
+        verify(listener, times(1)).onConversationsUpdate(capturedConversation.capture());
+        assertThat(capturedConversation.getValue()).containsExactly(conversationChannel,
+                conversationChannel2);
+    }
+
+    @Test
+    public void testAddOrUpdateStatusNotifiesConversationsListeners() throws Exception {
+        mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+        ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+                buildPerson());
+        mDataManager.addOrUpdateConversationInfo(shortcut);
+        PeopleService.ConversationsListener listener = mock(
+                PeopleService.ConversationsListener.class);
+        mDataManager.addConversationsListener(listener);
+
+        ConversationStatus status = new ConversationStatus.Builder("cs1", ACTIVITY_GAME).build();
+        mDataManager.addOrUpdateStatus(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, status);
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<List<ConversationChannel>> capturedConversation = ArgumentCaptor.forClass(
+                List.class);
+        verify(listener, times(1)).onConversationsUpdate(capturedConversation.capture());
+        ConversationChannel result = Iterables.getOnlyElement(capturedConversation.getValue());
+        assertThat(result.getStatuses()).containsExactly(status);
+        assertEquals(result.getShortcutInfo().getId(), TEST_SHORTCUT_ID);
+    }
+
+    @Test
     public void testGetConversationReturnsCustomizedConversation() {
         mDataManager.onUserUnlocked(USER_ID_PRIMARY);
 
@@ -975,7 +1060,7 @@
         mDataManager.reportShareTargetEvent(appTargetEvent, intentFilter);
         byte[] payload = mDataManager.getBackupPayload(USER_ID_PRIMARY);
 
-        DataManager dataManager = new DataManager(mContext, mInjector);
+        DataManager dataManager = new DataManager(mContext, mInjector, mLooper.getLooper());
         dataManager.onUserUnlocked(USER_ID_PRIMARY);
         dataManager.restore(USER_ID_PRIMARY, payload);
         ConversationInfo conversationInfo = dataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY)
diff --git a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java
index 03e60af..ddbe81c 100644
--- a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.powerstats;
 
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -34,6 +35,9 @@
 
 import com.android.server.SystemService;
 import com.android.server.powerstats.PowerStatsHALWrapper.IPowerStatsHALWrapper;
+import com.android.server.powerstats.ProtoStreamUtils.ChannelUtils;
+import com.android.server.powerstats.ProtoStreamUtils.EnergyConsumerUtils;
+import com.android.server.powerstats.ProtoStreamUtils.PowerEntityUtils;
 import com.android.server.powerstats.nano.PowerEntityProto;
 import com.android.server.powerstats.nano.PowerStatsServiceMeterProto;
 import com.android.server.powerstats.nano.PowerStatsServiceModelProto;
@@ -52,6 +56,7 @@
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.file.Files;
+import java.util.Arrays;
 import java.util.Random;
 
 /**
@@ -63,15 +68,18 @@
 public class PowerStatsServiceTest {
     private static final String TAG = PowerStatsServiceTest.class.getSimpleName();
     private static final String DATA_STORAGE_SUBDIR = "powerstatstest";
-    private static final String METER_FILENAME = "metertest";
-    private static final String MODEL_FILENAME = "modeltest";
-    private static final String RESIDENCY_FILENAME = "residencytest";
+    private static final String METER_FILENAME = "log.powerstats.metertest.0";
+    private static final String MODEL_FILENAME = "log.powerstats.modeltest.0";
+    private static final String RESIDENCY_FILENAME = "log.powerstats.residencytest.0";
     private static final String PROTO_OUTPUT_FILENAME = "powerstats.proto";
     private static final String CHANNEL_NAME = "channelname";
     private static final String CHANNEL_SUBSYSTEM = "channelsubsystem";
     private static final String POWER_ENTITY_NAME = "powerentityinfo";
     private static final String STATE_NAME = "stateinfo";
     private static final String ENERGY_CONSUMER_NAME = "energyconsumer";
+    private static final String METER_CACHE_FILENAME = "meterCacheTest";
+    private static final String MODEL_CACHE_FILENAME = "modelCacheTest";
+    private static final String RESIDENCY_CACHE_FILENAME = "residencyCacheTest";
     private static final int ENERGY_METER_COUNT = 8;
     private static final int ENERGY_CONSUMER_COUNT = 2;
     private static final int ENERGY_CONSUMER_ATTRIBUTION_COUNT = 5;
@@ -90,12 +98,12 @@
         private TestPowerStatsHALWrapper mTestPowerStatsHALWrapper = new TestPowerStatsHALWrapper();
         @Override
         File createDataStoragePath() {
-            mDataStorageDir = null;
-
-            try {
-                mDataStorageDir = Files.createTempDirectory(DATA_STORAGE_SUBDIR).toFile();
-            } catch (IOException e) {
-                fail("Could not create temp directory.");
+            if (mDataStorageDir == null) {
+                try {
+                    mDataStorageDir = Files.createTempDirectory(DATA_STORAGE_SUBDIR).toFile();
+                } catch (IOException e) {
+                    fail("Could not create temp directory.");
+                }
             }
 
             return mDataStorageDir;
@@ -117,16 +125,36 @@
         }
 
         @Override
+        String createMeterCacheFilename() {
+            return METER_CACHE_FILENAME;
+        }
+
+        @Override
+        String createModelCacheFilename() {
+            return MODEL_CACHE_FILENAME;
+        }
+
+        @Override
+        String createResidencyCacheFilename() {
+            return RESIDENCY_CACHE_FILENAME;
+        }
+
+        @Override
         IPowerStatsHALWrapper getPowerStatsHALWrapperImpl() {
             return mTestPowerStatsHALWrapper;
         }
 
         @Override
         PowerStatsLogger createPowerStatsLogger(Context context, File dataStoragePath,
-                String meterFilename, String modelFilename, String residencyFilename,
+                String meterFilename, String meterCacheFilename,
+                String modelFilename, String modelCacheFilename,
+                String residencyFilename, String residencyCacheFilename,
                 IPowerStatsHALWrapper powerStatsHALWrapper) {
-            mPowerStatsLogger = new PowerStatsLogger(context, dataStoragePath, meterFilename,
-                modelFilename, residencyFilename, powerStatsHALWrapper);
+            mPowerStatsLogger = new PowerStatsLogger(context, dataStoragePath,
+                meterFilename, meterCacheFilename,
+                modelFilename, modelCacheFilename,
+                residencyFilename, residencyCacheFilename,
+                powerStatsHALWrapper);
             return mPowerStatsLogger;
         }
 
@@ -665,4 +693,315 @@
         // input buffer had only length and no data.
         assertTrue(pssProto.stateResidencyResult.length == 0);
     }
+
+    @Test
+    public void testDataStorageDeletedMeterMismatch() throws IOException {
+        // Create the directory where cached data will be stored.
+        mInjector.createDataStoragePath();
+
+        // In order to create cached data that will match the current data read by the
+        // PowerStatsService we need to write valid data from the TestPowerStatsHALWrapper that is
+        // returned from the Injector.
+        IPowerStatsHALWrapper powerStatsHALWrapper = mInjector.getPowerStatsHALWrapperImpl();
+
+        // Generate random array of bytes to emulate cached meter data.  Store to file.
+        Random rd = new Random();
+        byte[] bytes = new byte[100];
+        rd.nextBytes(bytes);
+        File onDeviceStorageFile = new File(mDataStorageDir, mInjector.createMeterCacheFilename());
+        FileOutputStream onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+        onDeviceStorageFos.write(bytes);
+        onDeviceStorageFos.close();
+
+        // Create cached energy consumer data and write to file.
+        EnergyConsumer[] energyConsumers = powerStatsHALWrapper.getEnergyConsumerInfo();
+        bytes = EnergyConsumerUtils.getProtoBytes(energyConsumers);
+        onDeviceStorageFile = new File(mDataStorageDir, mInjector.createModelCacheFilename());
+        onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+        onDeviceStorageFos.write(bytes);
+        onDeviceStorageFos.close();
+
+        // Create cached power entity info data and write to file.
+        PowerEntity[] powerEntityInfo = powerStatsHALWrapper.getPowerEntityInfo();
+        bytes = PowerEntityUtils.getProtoBytes(powerEntityInfo);
+        onDeviceStorageFile = new File(mDataStorageDir, mInjector.createResidencyCacheFilename());
+        onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+        onDeviceStorageFos.write(bytes);
+        onDeviceStorageFos.close();
+
+        // Create log files.
+        File meterFile = new File(mDataStorageDir, mInjector.createMeterFilename());
+        File modelFile = new File(mDataStorageDir, mInjector.createModelFilename());
+        File residencyFile = new File(mDataStorageDir, mInjector.createResidencyFilename());
+        meterFile.createNewFile();
+        modelFile.createNewFile();
+        residencyFile.createNewFile();
+
+        // Verify log files exist.
+        assertTrue(meterFile.exists());
+        assertTrue(modelFile.exists());
+        assertTrue(residencyFile.exists());
+
+        // Boot device after creating old cached data.
+        mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+
+        // Since cached meter data is just random bytes it won't match the data read from the HAL.
+        // This mismatch of cached and current HAL data should force a delete.
+        assertTrue(mService.getDeleteMeterDataOnBoot());
+        assertFalse(mService.getDeleteModelDataOnBoot());
+        assertFalse(mService.getDeleteResidencyDataOnBoot());
+
+        // Verify log files were deleted.
+        assertFalse(meterFile.exists());
+        assertTrue(modelFile.exists());
+        assertTrue(residencyFile.exists());
+
+        // Verify cached meter data was updated to new HAL output.
+        Channel[] channels = powerStatsHALWrapper.getEnergyMeterInfo();
+        byte[] bytesExpected = ChannelUtils.getProtoBytes(channels);
+        onDeviceStorageFile = new File(mDataStorageDir, mInjector.createMeterCacheFilename());
+        byte[] bytesActual = new byte[(int) onDeviceStorageFile.length()];
+        FileInputStream onDeviceStorageFis = new FileInputStream(onDeviceStorageFile);
+        onDeviceStorageFis.read(bytesActual);
+        assertTrue(Arrays.equals(bytesExpected, bytesActual));
+    }
+
+    @Test
+    public void testDataStorageDeletedModelMismatch() throws IOException {
+        // Create the directory where cached data will be stored.
+        mInjector.createDataStoragePath();
+
+        // In order to create cached data that will match the current data read by the
+        // PowerStatsService we need to write valid data from the TestPowerStatsHALWrapper that is
+        // returned from the Injector.
+        IPowerStatsHALWrapper powerStatsHALWrapper = mInjector.getPowerStatsHALWrapperImpl();
+
+        // Create cached channel data and write to file.
+        Channel[] channels = powerStatsHALWrapper.getEnergyMeterInfo();
+        byte[] bytes = ChannelUtils.getProtoBytes(channels);
+        File onDeviceStorageFile = new File(mDataStorageDir, mInjector.createMeterCacheFilename());
+        FileOutputStream onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+        onDeviceStorageFos.write(bytes);
+        onDeviceStorageFos.close();
+
+        // Generate random array of bytes to emulate cached energy consumer data.  Store to file.
+        Random rd = new Random();
+        bytes = new byte[100];
+        rd.nextBytes(bytes);
+        onDeviceStorageFile = new File(mDataStorageDir, mInjector.createModelCacheFilename());
+        onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+        onDeviceStorageFos.write(bytes);
+        onDeviceStorageFos.close();
+
+        // Create cached power entity info data and write to file.
+        PowerEntity[] powerEntityInfo = powerStatsHALWrapper.getPowerEntityInfo();
+        bytes = PowerEntityUtils.getProtoBytes(powerEntityInfo);
+        onDeviceStorageFile = new File(mDataStorageDir, mInjector.createResidencyCacheFilename());
+        onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+        onDeviceStorageFos.write(bytes);
+        onDeviceStorageFos.close();
+
+        // Create log files.
+        File meterFile = new File(mDataStorageDir, mInjector.createMeterFilename());
+        File modelFile = new File(mDataStorageDir, mInjector.createModelFilename());
+        File residencyFile = new File(mDataStorageDir, mInjector.createResidencyFilename());
+        meterFile.createNewFile();
+        modelFile.createNewFile();
+        residencyFile.createNewFile();
+
+        // Verify log files exist.
+        assertTrue(meterFile.exists());
+        assertTrue(modelFile.exists());
+        assertTrue(residencyFile.exists());
+
+        // Boot device after creating old cached data.
+        mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+
+        // Since cached energy consumer data is just random bytes it won't match the data read from
+        // the HAL.  This mismatch of cached and current HAL data should force a delete.
+        assertFalse(mService.getDeleteMeterDataOnBoot());
+        assertTrue(mService.getDeleteModelDataOnBoot());
+        assertFalse(mService.getDeleteResidencyDataOnBoot());
+
+        // Verify log files were deleted.
+        assertTrue(meterFile.exists());
+        assertFalse(modelFile.exists());
+        assertTrue(residencyFile.exists());
+
+        // Verify cached energy consumer data was updated to new HAL output.
+        EnergyConsumer[] energyConsumers = powerStatsHALWrapper.getEnergyConsumerInfo();
+        byte[] bytesExpected = EnergyConsumerUtils.getProtoBytes(energyConsumers);
+        onDeviceStorageFile = new File(mDataStorageDir, mInjector.createModelCacheFilename());
+        byte[] bytesActual = new byte[(int) onDeviceStorageFile.length()];
+        FileInputStream onDeviceStorageFis = new FileInputStream(onDeviceStorageFile);
+        onDeviceStorageFis.read(bytesActual);
+        assertTrue(Arrays.equals(bytesExpected, bytesActual));
+    }
+
+    @Test
+    public void testDataStorageDeletedResidencyMismatch() throws IOException {
+        // Create the directory where cached data will be stored.
+        mInjector.createDataStoragePath();
+
+        // In order to create cached data that will match the current data read by the
+        // PowerStatsService we need to write valid data from the TestPowerStatsHALWrapper that is
+        // returned from the Injector.
+        IPowerStatsHALWrapper powerStatsHALWrapper = mInjector.getPowerStatsHALWrapperImpl();
+
+        // Create cached channel data and write to file.
+        Channel[] channels = powerStatsHALWrapper.getEnergyMeterInfo();
+        byte[] bytes = ChannelUtils.getProtoBytes(channels);
+        File onDeviceStorageFile = new File(mDataStorageDir, mInjector.createMeterCacheFilename());
+        FileOutputStream onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+        onDeviceStorageFos.write(bytes);
+        onDeviceStorageFos.close();
+
+        // Create cached energy consumer data and write to file.
+        EnergyConsumer[] energyConsumers = powerStatsHALWrapper.getEnergyConsumerInfo();
+        bytes = EnergyConsumerUtils.getProtoBytes(energyConsumers);
+        onDeviceStorageFile = new File(mDataStorageDir, mInjector.createModelCacheFilename());
+        onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+        onDeviceStorageFos.write(bytes);
+        onDeviceStorageFos.close();
+
+        // Generate random array of bytes to emulate cached power entity info data.  Store to file.
+        Random rd = new Random();
+        bytes = new byte[100];
+        rd.nextBytes(bytes);
+        onDeviceStorageFile = new File(mDataStorageDir, mInjector.createResidencyCacheFilename());
+        onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+        onDeviceStorageFos.write(bytes);
+        onDeviceStorageFos.close();
+
+        // Create log files.
+        File meterFile = new File(mDataStorageDir, mInjector.createMeterFilename());
+        File modelFile = new File(mDataStorageDir, mInjector.createModelFilename());
+        File residencyFile = new File(mDataStorageDir, mInjector.createResidencyFilename());
+        meterFile.createNewFile();
+        modelFile.createNewFile();
+        residencyFile.createNewFile();
+
+        // Verify log files exist.
+        assertTrue(meterFile.exists());
+        assertTrue(modelFile.exists());
+        assertTrue(residencyFile.exists());
+
+        // Boot device after creating old cached data.
+        mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+
+        // Since cached power entity info data is just random bytes it won't match the data read
+        // from the HAL.  This mismatch of cached and current HAL data should force a delete.
+        assertFalse(mService.getDeleteMeterDataOnBoot());
+        assertFalse(mService.getDeleteModelDataOnBoot());
+        assertTrue(mService.getDeleteResidencyDataOnBoot());
+
+        // Verify log files were deleted.
+        assertTrue(meterFile.exists());
+        assertTrue(modelFile.exists());
+        assertFalse(residencyFile.exists());
+
+        // Verify cached power entity data was updated to new HAL output.
+        PowerEntity[] powerEntityInfo = powerStatsHALWrapper.getPowerEntityInfo();
+        byte[] bytesExpected = PowerEntityUtils.getProtoBytes(powerEntityInfo);
+        onDeviceStorageFile = new File(mDataStorageDir, mInjector.createResidencyCacheFilename());
+        byte[] bytesActual = new byte[(int) onDeviceStorageFile.length()];
+        FileInputStream onDeviceStorageFis = new FileInputStream(onDeviceStorageFile);
+        onDeviceStorageFis.read(bytesActual);
+        assertTrue(Arrays.equals(bytesExpected, bytesActual));
+    }
+
+    @Test
+    public void testDataStorageNotDeletedNoCachedData() throws IOException {
+        // Create the directory where log files will be stored.
+        mInjector.createDataStoragePath();
+
+        // Create log files.
+        File meterFile = new File(mDataStorageDir, mInjector.createMeterFilename());
+        File modelFile = new File(mDataStorageDir, mInjector.createModelFilename());
+        File residencyFile = new File(mDataStorageDir, mInjector.createResidencyFilename());
+        meterFile.createNewFile();
+        modelFile.createNewFile();
+        residencyFile.createNewFile();
+
+        // Verify log files exist.
+        assertTrue(meterFile.exists());
+        assertTrue(modelFile.exists());
+        assertTrue(residencyFile.exists());
+
+        // This test mimics the device's first boot where there is no cached data.
+        mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+
+        // Since there is no cached data on the first boot any log files that happen to exist
+        // should be deleted.
+        assertTrue(mService.getDeleteMeterDataOnBoot());
+        assertTrue(mService.getDeleteModelDataOnBoot());
+        assertTrue(mService.getDeleteResidencyDataOnBoot());
+
+        // Verify log files were deleted.
+        assertFalse(meterFile.exists());
+        assertFalse(modelFile.exists());
+        assertFalse(residencyFile.exists());
+    }
+
+    @Test
+    public void testDataStorageNotDeletedAllDataMatches() throws IOException {
+        // Create the directory where cached data will be stored.
+        mInjector.createDataStoragePath();
+
+        // In order to create cached data that will match the current data read by the
+        // PowerStatsService we need to write valid data from the TestPowerStatsHALWrapper that is
+        // returned from the Injector.
+        IPowerStatsHALWrapper powerStatsHALWrapper = mInjector.getPowerStatsHALWrapperImpl();
+
+        // Create cached channel data and write to file.
+        Channel[] channels = powerStatsHALWrapper.getEnergyMeterInfo();
+        byte[] bytes = ChannelUtils.getProtoBytes(channels);
+        File onDeviceStorageFile = new File(mDataStorageDir, mInjector.createMeterCacheFilename());
+        FileOutputStream onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+        onDeviceStorageFos.write(bytes);
+        onDeviceStorageFos.close();
+
+        // Create cached energy consumer data and write to file.
+        EnergyConsumer[] energyConsumers = powerStatsHALWrapper.getEnergyConsumerInfo();
+        bytes = EnergyConsumerUtils.getProtoBytes(energyConsumers);
+        onDeviceStorageFile = new File(mDataStorageDir, mInjector.createModelCacheFilename());
+        onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+        onDeviceStorageFos.write(bytes);
+        onDeviceStorageFos.close();
+
+        // Create cached power entity info data and write to file.
+        PowerEntity[] powerEntityInfo = powerStatsHALWrapper.getPowerEntityInfo();
+        bytes = PowerEntityUtils.getProtoBytes(powerEntityInfo);
+        onDeviceStorageFile = new File(mDataStorageDir, mInjector.createResidencyCacheFilename());
+        onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+        onDeviceStorageFos.write(bytes);
+        onDeviceStorageFos.close();
+
+        // Create log files.
+        File meterFile = new File(mDataStorageDir, mInjector.createMeterFilename());
+        File modelFile = new File(mDataStorageDir, mInjector.createModelFilename());
+        File residencyFile = new File(mDataStorageDir, mInjector.createResidencyFilename());
+        meterFile.createNewFile();
+        modelFile.createNewFile();
+        residencyFile.createNewFile();
+
+        // Verify log files exist.
+        assertTrue(meterFile.exists());
+        assertTrue(modelFile.exists());
+        assertTrue(residencyFile.exists());
+
+        // Boot device after creating old cached data.
+        mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+
+        // All cached data created above should match current data read in PowerStatsService so we
+        // expect the data not to be deleted.
+        assertFalse(mService.getDeleteMeterDataOnBoot());
+        assertFalse(mService.getDeleteModelDataOnBoot());
+        assertFalse(mService.getDeleteResidencyDataOnBoot());
+
+        // Verify log files were not deleted.
+        assertTrue(meterFile.exists());
+        assertTrue(modelFile.exists());
+        assertTrue(residencyFile.exists());
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 15e045c..2fdd63e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -16,8 +16,11 @@
 
 package com.android.server.wm;
 
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
+import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
+import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
 import static android.view.WindowManager.TRANSIT_OLD_NONE;
 import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
 
@@ -94,10 +97,19 @@
         mController = new RemoteAnimationController(mWm, mAdapter, mHandler);
     }
 
+    private WindowState createAppOverlayWindow() {
+        final WindowState win = createWindow(null /* parent */, TYPE_APPLICATION_OVERLAY,
+                "testOverlayWindow");
+        win.mActivityRecord = null;
+        win.mHasSurface = true;
+        return win;
+    }
+
     @Test
     public void testRun() throws Exception {
         final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
         mDisplayContent.mOpeningApps.add(win.mActivityRecord);
+        final WindowState overlayWin = createAppOverlayWindow();
         try {
             final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mActivityRecord,
                     new Point(50, 100), null, new Rect(50, 100, 150, 150), null).mAdapter;
@@ -109,12 +121,12 @@
                     ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
             final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
                     ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
-            final ArgumentCaptor<RemoteAnimationTarget[]> nonApsCaptor =
+            final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor =
                     ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
             final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
                     ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
             verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_ACTIVITY_OPEN),
-                    appsCaptor.capture(), wallpapersCaptor.capture(), nonApsCaptor.capture(),
+                    appsCaptor.capture(), wallpapersCaptor.capture(), nonAppsCaptor.capture(),
                     finishedCaptor.capture());
             assertEquals(1, appsCaptor.getValue().length);
             final RemoteAnimationTarget app = appsCaptor.getValue()[0];
@@ -130,6 +142,7 @@
             finishedCaptor.getValue().onAnimationFinished();
             verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION),
                     eq(adapter));
+            assertEquals(0, nonAppsCaptor.getValue().length);
         } finally {
             mDisplayContent.mOpeningApps.clear();
         }
@@ -424,6 +437,89 @@
         }
     }
 
+    @Test
+    public void testNonAppIncluded_keygaurdGoingAway() throws Exception {
+        final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+        mDisplayContent.mOpeningApps.add(win.mActivityRecord);
+        // Add overlay window hidden by the keyguard.
+        final WindowState overlayWin = createAppOverlayWindow();
+        overlayWin.hide(false /* doAnimation */, false /* requestAnim */);
+        try {
+            final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
+                    win.mActivityRecord, new Point(50, 100), null,
+                    new Rect(50, 100, 150, 150), null).mAdapter;
+            adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
+                    mFinishedCallback);
+            mController.goodToGo(TRANSIT_OLD_KEYGUARD_GOING_AWAY);
+            mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+            final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
+                    ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+            final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
+                    ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+            final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor =
+                    ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+            final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
+                    ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
+            verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_KEYGUARD_GOING_AWAY),
+                    appsCaptor.capture(), wallpapersCaptor.capture(), nonAppsCaptor.capture(),
+                    finishedCaptor.capture());
+            assertEquals(1, appsCaptor.getValue().length);
+            final RemoteAnimationTarget app = appsCaptor.getValue()[0];
+            assertEquals(new Point(50, 100), app.position);
+            assertEquals(new Rect(50, 100, 150, 150), app.sourceContainerBounds);
+            assertEquals(win.mActivityRecord.getPrefixOrderIndex(), app.prefixOrderIndex);
+            assertEquals(win.mActivityRecord.getTask().mTaskId, app.taskId);
+            assertEquals(mMockLeash, app.leash);
+            assertEquals(false, app.isTranslucent);
+            verify(mMockTransaction).setPosition(mMockLeash, app.position.x, app.position.y);
+            verify(mMockTransaction).setWindowCrop(mMockLeash, 100, 50);
+
+            finishedCaptor.getValue().onAnimationFinished();
+            verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION),
+                    eq(adapter));
+            assertEquals(1, nonAppsCaptor.getValue().length);
+        } finally {
+            mDisplayContent.mOpeningApps.clear();
+        }
+    }
+
+    @Test
+    public void testNonAppIncluded_keygaurdGoingAwayToWallpaper() throws Exception {
+        final WindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm, mock(IBinder.class),
+                true, mDisplayContent, true /* ownerCanManageAppTokens */);
+        spyOn(mDisplayContent.mWallpaperController);
+        doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible();
+        final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+        mDisplayContent.mOpeningApps.add(win.mActivityRecord);
+        // Add overlay window hidden by the keyguard.
+        final WindowState overlayWin = createAppOverlayWindow();
+        overlayWin.hide(false /* doAnimation */, false /* requestAnim */);
+        try {
+            final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
+                    win.mActivityRecord, new Point(50, 100), null,
+                    new Rect(50, 100, 150, 150), null).mAdapter;
+            adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
+                    mFinishedCallback);
+            mController.goodToGo(TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER);
+            mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+            final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
+                    ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+            final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
+                    ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+            final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor =
+                    ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+            final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
+                    ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
+            verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER),
+                    appsCaptor.capture(), wallpapersCaptor.capture(), nonAppsCaptor.capture(),
+                    finishedCaptor.capture());
+            assertEquals(1, wallpapersCaptor.getValue().length);
+            assertEquals(1, nonAppsCaptor.getValue().length);
+        } finally {
+            mDisplayContent.mOpeningApps.clear();
+        }
+    }
+
     private static void verifyNoMoreInteractionsExceptAsBinder(IInterface binder) {
         verify(binder, atLeast(0)).asBinder();
         verifyNoMoreInteractions(binder);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index e4b865f..86d8eee 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -91,11 +91,6 @@
         return attrs.type == TYPE_NOTIFICATION_SHADE;
     }
 
-    @Override
-    public boolean canBeHiddenByKeyguardLw(WindowState win) {
-        return false;
-    }
-
     /**
      * Sets a runnable to run when adding a splash screen which gets executed after the window has
      * been added but before returning the surface.
diff --git a/services/texttospeech/Android.bp b/services/texttospeech/Android.bp
deleted file mode 100644
index bacc932..0000000
--- a/services/texttospeech/Android.bp
+++ /dev/null
@@ -1,13 +0,0 @@
-filegroup {
-    name: "services.texttospeech-sources",
-    srcs: ["java/**/*.java"],
-    path: "java",
-    visibility: ["//frameworks/base/services"],
-}
-
-java_library_static {
-    name: "services.texttospeech",
-    defaults: ["platform_service_defaults"],
-    srcs: [":services.texttospeech-sources"],
-    libs: ["services.core"],
-}
\ No newline at end of file
diff --git a/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerPerUserService.java b/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerPerUserService.java
deleted file mode 100644
index f805904..0000000
--- a/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerPerUserService.java
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * 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.texttospeech;
-
-import static com.android.internal.infra.AbstractRemoteService.PERMANENT_BOUND_TIMEOUT_MS;
-
-import android.annotation.NonNull;
-import android.annotation.UserIdInt;
-import android.app.AppGlobals;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ServiceInfo;
-import android.os.IBinder.DeathRecipient;
-import android.os.RemoteException;
-import android.speech.tts.ITextToSpeechService;
-import android.speech.tts.ITextToSpeechSession;
-import android.speech.tts.ITextToSpeechSessionCallback;
-import android.speech.tts.TextToSpeech;
-import android.util.Slog;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.infra.ServiceConnector;
-import com.android.server.infra.AbstractPerUserSystemService;
-
-import java.util.NoSuchElementException;
-
-/**
- * Manages per-user text to speech session activated by {@link TextToSpeechManagerService}.
- * Creates {@link TtsClient} interface object with direct connection to
- * {@link android.speech.tts.TextToSpeechService} provider.
- *
- * @see ITextToSpeechSession
- * @see TextToSpeech
- */
-final class TextToSpeechManagerPerUserService extends
-        AbstractPerUserSystemService<TextToSpeechManagerPerUserService,
-                TextToSpeechManagerService> {
-
-    private static final String TAG = TextToSpeechManagerPerUserService.class.getSimpleName();
-
-    TextToSpeechManagerPerUserService(
-            @NonNull TextToSpeechManagerService master,
-            @NonNull Object lock, @UserIdInt int userId) {
-        super(master, lock, userId);
-    }
-
-    void createSessionLocked(String engine, ITextToSpeechSessionCallback sessionCallback) {
-        TextToSpeechSessionConnection.start(getContext(), mUserId, engine, sessionCallback);
-    }
-
-    @GuardedBy("mLock")
-    @Override // from PerUserSystemService
-    @NonNull
-    protected ServiceInfo newServiceInfoLocked(
-            @SuppressWarnings("unused") @NonNull ComponentName serviceComponent)
-            throws PackageManager.NameNotFoundException {
-        try {
-            return AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
-                    PackageManager.GET_META_DATA, mUserId);
-        } catch (RemoteException e) {
-            throw new PackageManager.NameNotFoundException(
-                    "Could not get service for " + serviceComponent);
-        }
-    }
-
-    private static class TextToSpeechSessionConnection extends
-            ServiceConnector.Impl<ITextToSpeechService> {
-
-        private final String mEngine;
-        private final ITextToSpeechSessionCallback mCallback;
-        private final DeathRecipient mUnbindOnDeathHandler;
-
-        static void start(Context context, @UserIdInt int userId, String engine,
-                ITextToSpeechSessionCallback callback) {
-            new TextToSpeechSessionConnection(context, userId, engine, callback).start();
-        }
-
-        private TextToSpeechSessionConnection(Context context, @UserIdInt int userId, String engine,
-                ITextToSpeechSessionCallback callback) {
-            super(context,
-                    new Intent(TextToSpeech.Engine.INTENT_ACTION_TTS_SERVICE).setPackage(engine),
-                    Context.BIND_AUTO_CREATE,
-                    userId,
-                    ITextToSpeechService.Stub::asInterface);
-            mEngine = engine;
-            mCallback = callback;
-            mUnbindOnDeathHandler = () -> unbindEngine("client process death is reported");
-        }
-
-        private void start() {
-            Slog.d(TAG, "Trying to start connection to TTS engine: " + mEngine);
-
-            connect()
-                    .thenAccept(
-                            serviceBinder -> {
-                                if (serviceBinder != null) {
-                                    Slog.d(TAG,
-                                            "Connected successfully to TTS engine: " + mEngine);
-                                    try {
-                                        mCallback.onConnected(new ITextToSpeechSession.Stub() {
-                                            @Override
-                                            public void disconnect() {
-                                                unbindEngine("client disconnection request");
-                                            }
-                                        }, serviceBinder.asBinder());
-
-                                        mCallback.asBinder().linkToDeath(mUnbindOnDeathHandler, 0);
-                                    } catch (RemoteException ex) {
-                                        Slog.w(TAG, "Error notifying the client on connection", ex);
-
-                                        unbindEngine(
-                                                "failed communicating with the client - process "
-                                                        + "is dead");
-                                    }
-                                } else {
-                                    Slog.w(TAG, "Failed to obtain TTS engine binder");
-                                    runSessionCallbackMethod(
-                                            () -> mCallback.onError("Failed creating TTS session"));
-                                }
-                            })
-                    .exceptionally(ex -> {
-                        Slog.w(TAG, "TTS engine binding error", ex);
-                        runSessionCallbackMethod(
-                                () -> mCallback.onError(
-                                        "Failed creating TTS session: " + ex.getCause()));
-
-                        return null;
-                    });
-        }
-
-        @Override // from ServiceConnector.Impl
-        protected void onServiceConnectionStatusChanged(
-                ITextToSpeechService service, boolean connected) {
-            if (!connected) {
-                Slog.w(TAG, "Disconnected from TTS engine");
-                runSessionCallbackMethod(mCallback::onDisconnected);
-
-                try {
-                    mCallback.asBinder().unlinkToDeath(mUnbindOnDeathHandler, 0);
-                } catch (NoSuchElementException ex) {
-                    Slog.d(TAG, "The death recipient was not linked.");
-                }
-            }
-        }
-
-        @Override // from ServiceConnector.Impl
-        protected long getAutoDisconnectTimeoutMs() {
-            return PERMANENT_BOUND_TIMEOUT_MS;
-        }
-
-        private void unbindEngine(String reason) {
-            Slog.d(TAG, "Unbinding TTS engine: " + mEngine + ". Reason: " + reason);
-            unbind();
-        }
-    }
-
-    static void runSessionCallbackMethod(ThrowingRunnable callbackRunnable) {
-        try {
-            callbackRunnable.runOrThrow();
-        } catch (RemoteException ex) {
-            Slog.w(TAG, "Failed running callback method", ex);
-        }
-    }
-
-    interface ThrowingRunnable {
-        void runOrThrow() throws RemoteException;
-    }
-}
diff --git a/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerService.java b/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerService.java
deleted file mode 100644
index 9015563..0000000
--- a/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerService.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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.texttospeech;
-
-import static com.android.server.texttospeech.TextToSpeechManagerPerUserService.runSessionCallbackMethod;
-
-import android.annotation.NonNull;
-import android.annotation.UserIdInt;
-import android.content.Context;
-import android.os.UserHandle;
-import android.speech.tts.ITextToSpeechManager;
-import android.speech.tts.ITextToSpeechSessionCallback;
-
-import com.android.server.infra.AbstractMasterSystemService;
-
-
-/**
- * A service that  allows secured synthesizing of text to speech audio. Upon request creates a
- * session
- * that is managed by {@link TextToSpeechManagerPerUserService}.
- *
- * @see ITextToSpeechManager
- */
-public final class TextToSpeechManagerService extends
-        AbstractMasterSystemService<TextToSpeechManagerService,
-                TextToSpeechManagerPerUserService> {
-
-    private static final String TAG = TextToSpeechManagerService.class.getSimpleName();
-
-    public TextToSpeechManagerService(@NonNull Context context) {
-        super(context, /* serviceNameResolver= */ null,
-                /* disallowProperty = */null);
-    }
-
-    @Override // from SystemService
-    public void onStart() {
-        publishBinderService(Context.TEXT_TO_SPEECH_MANAGER_SERVICE,
-                new TextToSpeechManagerServiceStub());
-    }
-
-    @Override
-    protected TextToSpeechManagerPerUserService newServiceLocked(
-            @UserIdInt int resolvedUserId, boolean disabled) {
-        return new TextToSpeechManagerPerUserService(this, mLock, resolvedUserId);
-    }
-
-    private final class TextToSpeechManagerServiceStub extends ITextToSpeechManager.Stub {
-        @Override
-        public void createSession(String engine,
-                ITextToSpeechSessionCallback sessionCallback) {
-            synchronized (mLock) {
-                TextToSpeechManagerPerUserService perUserService = getServiceForUserLocked(
-                        UserHandle.getCallingUserId());
-                if (perUserService != null) {
-                    perUserService.createSessionLocked(engine, sessionCallback);
-                } else {
-                    runSessionCallbackMethod(
-                            () -> sessionCallback.onError("Service is not available for user"));
-                }
-            }
-        }
-    }
-}
diff --git a/core/java/android/speech/tts/ITextToSpeechSession.aidl b/telecomm/java/android/telecom/CallScreeningService.aidl
similarity index 61%
rename from core/java/android/speech/tts/ITextToSpeechSession.aidl
rename to telecomm/java/android/telecom/CallScreeningService.aidl
index b2afeb0..87b5138 100644
--- a/core/java/android/speech/tts/ITextToSpeechSession.aidl
+++ b/telecomm/java/android/telecom/CallScreeningService.aidl
@@ -11,23 +11,12 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License.
+ * limitations under the License
  */
 
-package android.speech.tts;
+package android.telecom;
 
 /**
- * TextToSpeech session interface. Allows to control remote TTS service session once connected.
- *
- * @see ITextToSpeechManager
- * @see ITextToSpeechSessionCallback
- *
  * {@hide}
  */
-oneway interface ITextToSpeechSession {
-
-    /**
-     * Disconnects the client from the TTS provider.
-     */
-    void disconnect();
-}
\ No newline at end of file
+parcelable CallScreeningService.ParcelableCallResponse;
diff --git a/telecomm/java/android/telecom/CallScreeningService.java b/telecomm/java/android/telecom/CallScreeningService.java
index 7988b03..deeb433 100644
--- a/telecomm/java/android/telecom/CallScreeningService.java
+++ b/telecomm/java/android/telecom/CallScreeningService.java
@@ -17,6 +17,7 @@
 package android.telecom;
 
 import android.Manifest;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
@@ -30,12 +31,18 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.os.RemoteException;
 
 import com.android.internal.os.SomeArgs;
 import com.android.internal.telecom.ICallScreeningAdapter;
 import com.android.internal.telecom.ICallScreeningService;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
 /**
  * This service can be implemented by the default dialer (see
  * {@link TelecomManager#getDefaultDialerPackage()}) or a third party app to allow or disallow
@@ -132,7 +139,10 @@
                                 .createFromParcelableCall((ParcelableCall) args.arg2);
                         onScreenCall(callDetails);
                         if (callDetails.getCallDirection() == Call.Details.DIRECTION_OUTGOING) {
-                            mCallScreeningAdapter.allowCall(callDetails.getTelecomCallId());
+                            mCallScreeningAdapter.onScreeningResponse(
+                                    callDetails.getTelecomCallId(),
+                                    new ComponentName(getPackageName(), getClass().getName()),
+                                    null);
                         }
                     } catch (RemoteException e) {
                         Log.w(this, "Exception when screening call: " + e);
@@ -157,10 +167,11 @@
 
     private ICallScreeningAdapter mCallScreeningAdapter;
 
-    /*
-     * Information about how to respond to an incoming call.
+    /**
+     * Parcelable version of {@link CallResponse} used to do IPC.
+     * @hide
      */
-    public static class CallResponse {
+    public static class ParcelableCallResponse implements Parcelable {
         private final boolean mShouldDisallowCall;
         private final boolean mShouldRejectCall;
         private final boolean mShouldSilenceCall;
@@ -168,13 +179,168 @@
         private final boolean mShouldSkipNotification;
         private final boolean mShouldScreenCallViaAudioProcessing;
 
+        private final int mCallComposerAttachmentsToShow;
+
+        private ParcelableCallResponse(
+                boolean shouldDisallowCall,
+                boolean shouldRejectCall,
+                boolean shouldSilenceCall,
+                boolean shouldSkipCallLog,
+                boolean shouldSkipNotification,
+                boolean shouldScreenCallViaAudioProcessing,
+                int callComposerAttachmentsToShow) {
+            mShouldDisallowCall = shouldDisallowCall;
+            mShouldRejectCall = shouldRejectCall;
+            mShouldSilenceCall = shouldSilenceCall;
+            mShouldSkipCallLog = shouldSkipCallLog;
+            mShouldSkipNotification = shouldSkipNotification;
+            mShouldScreenCallViaAudioProcessing = shouldScreenCallViaAudioProcessing;
+            mCallComposerAttachmentsToShow = callComposerAttachmentsToShow;
+        }
+
+        protected ParcelableCallResponse(Parcel in) {
+            mShouldDisallowCall = in.readBoolean();
+            mShouldRejectCall = in.readBoolean();
+            mShouldSilenceCall = in.readBoolean();
+            mShouldSkipCallLog = in.readBoolean();
+            mShouldSkipNotification = in.readBoolean();
+            mShouldScreenCallViaAudioProcessing = in.readBoolean();
+            mCallComposerAttachmentsToShow = in.readInt();
+        }
+
+        public CallResponse toCallResponse() {
+            return new CallResponse.Builder()
+                    .setDisallowCall(mShouldDisallowCall)
+                    .setRejectCall(mShouldRejectCall)
+                    .setSilenceCall(mShouldSilenceCall)
+                    .setSkipCallLog(mShouldSkipCallLog)
+                    .setSkipNotification(mShouldSkipNotification)
+                    .setShouldScreenCallViaAudioProcessing(mShouldScreenCallViaAudioProcessing)
+                    .setCallComposerAttachmentsToShow(mCallComposerAttachmentsToShow)
+                    .build();
+        }
+
+        public boolean shouldDisallowCall() {
+            return mShouldDisallowCall;
+        }
+
+        public boolean shouldRejectCall() {
+            return mShouldRejectCall;
+        }
+
+        public boolean shouldSilenceCall() {
+            return mShouldSilenceCall;
+        }
+
+        public boolean shouldSkipCallLog() {
+            return mShouldSkipCallLog;
+        }
+
+        public boolean shouldSkipNotification() {
+            return mShouldSkipNotification;
+        }
+
+        public boolean shouldScreenCallViaAudioProcessing() {
+            return mShouldScreenCallViaAudioProcessing;
+        }
+
+        public int getCallComposerAttachmentsToShow() {
+            return mCallComposerAttachmentsToShow;
+        }
+
+        public static final Creator<ParcelableCallResponse> CREATOR =
+                new Creator<ParcelableCallResponse>() {
+                    @Override
+                    public ParcelableCallResponse createFromParcel(Parcel in) {
+                        return new ParcelableCallResponse(in);
+                    }
+
+                    @Override
+                    public ParcelableCallResponse[] newArray(int size) {
+                        return new ParcelableCallResponse[size];
+                    }
+                };
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeBoolean(mShouldDisallowCall);
+            dest.writeBoolean(mShouldRejectCall);
+            dest.writeBoolean(mShouldSilenceCall);
+            dest.writeBoolean(mShouldSkipCallLog);
+            dest.writeBoolean(mShouldSkipNotification);
+            dest.writeBoolean(mShouldScreenCallViaAudioProcessing);
+            dest.writeInt(mCallComposerAttachmentsToShow);
+        }
+    }
+
+    /**
+     * Information about how to respond to an incoming call. Call screening apps can construct an
+     * instance of this class using {@link CallResponse.Builder}.
+     */
+    public static class CallResponse {
+        /**
+         * Bit flag indicating whether to show the picture attachment for call composer.
+         *
+         * Used with {@link Builder#setCallComposerAttachmentsToShow(int)}.
+         */
+        public static final int CALL_COMPOSER_ATTACHMENT_PICTURE = 1;
+
+        /**
+         * Bit flag indicating whether to show the location attachment for call composer.
+         *
+         * Used with {@link Builder#setCallComposerAttachmentsToShow(int)}.
+         */
+        public static final int CALL_COMPOSER_ATTACHMENT_LOCATION = 1 << 1;
+
+        /**
+         * Bit flag indicating whether to show the subject attachment for call composer.
+         *
+         * Used with {@link Builder#setCallComposerAttachmentsToShow(int)}.
+         */
+        public static final int CALL_COMPOSER_ATTACHMENT_SUBJECT = 1 << 2;
+
+        /**
+         * Bit flag indicating whether to show the priority attachment for call composer.
+         *
+         * Used with {@link Builder#setCallComposerAttachmentsToShow(int)}.
+         */
+        public static final int CALL_COMPOSER_ATTACHMENT_PRIORITY = 1 << 3;
+
+        /** @hide */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(prefix = "CALL_COMPOSER_ATTACHMENT_", flag = true,
+                value = {
+                        CALL_COMPOSER_ATTACHMENT_PICTURE,
+                        CALL_COMPOSER_ATTACHMENT_LOCATION,
+                        CALL_COMPOSER_ATTACHMENT_SUBJECT,
+                        CALL_COMPOSER_ATTACHMENT_PRIORITY
+                }
+        )
+        public @interface CallComposerAttachmentType {}
+
+        private static final int NUM_CALL_COMPOSER_ATTACHMENT_TYPES = 4;
+
+        private final boolean mShouldDisallowCall;
+        private final boolean mShouldRejectCall;
+        private final boolean mShouldSilenceCall;
+        private final boolean mShouldSkipCallLog;
+        private final boolean mShouldSkipNotification;
+        private final boolean mShouldScreenCallViaAudioProcessing;
+        private final int mCallComposerAttachmentsToShow;
+
         private CallResponse(
                 boolean shouldDisallowCall,
                 boolean shouldRejectCall,
                 boolean shouldSilenceCall,
                 boolean shouldSkipCallLog,
                 boolean shouldSkipNotification,
-                boolean shouldScreenCallViaAudioProcessing) {
+                boolean shouldScreenCallViaAudioProcessing,
+                int callComposerAttachmentsToShow) {
             if (!shouldDisallowCall
                     && (shouldRejectCall || shouldSkipCallLog || shouldSkipNotification)) {
                 throw new IllegalStateException("Invalid response state for allowed call.");
@@ -190,6 +356,7 @@
             mShouldSkipNotification = shouldSkipNotification;
             mShouldSilenceCall = shouldSilenceCall;
             mShouldScreenCallViaAudioProcessing = shouldScreenCallViaAudioProcessing;
+            mCallComposerAttachmentsToShow = callComposerAttachmentsToShow;
         }
 
         /*
@@ -237,6 +404,49 @@
             return mShouldScreenCallViaAudioProcessing;
         }
 
+        /**
+         * @return A bitmask of call composer attachments that should be shown to the user.
+         */
+        public @CallComposerAttachmentType int getCallComposerAttachmentsToShow() {
+            return mCallComposerAttachmentsToShow;
+        }
+
+        /** @hide */
+        public ParcelableCallResponse toParcelable() {
+            return new ParcelableCallResponse(
+                    mShouldDisallowCall,
+                    mShouldRejectCall,
+                    mShouldSilenceCall,
+                    mShouldSkipCallLog,
+                    mShouldSkipNotification,
+                    mShouldScreenCallViaAudioProcessing,
+                    mCallComposerAttachmentsToShow
+            );
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            CallResponse that = (CallResponse) o;
+            return mShouldDisallowCall == that.mShouldDisallowCall &&
+                    mShouldRejectCall == that.mShouldRejectCall &&
+                    mShouldSilenceCall == that.mShouldSilenceCall &&
+                    mShouldSkipCallLog == that.mShouldSkipCallLog &&
+                    mShouldSkipNotification == that.mShouldSkipNotification &&
+                    mShouldScreenCallViaAudioProcessing
+                            == that.mShouldScreenCallViaAudioProcessing &&
+                    mCallComposerAttachmentsToShow == that.mCallComposerAttachmentsToShow;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mShouldDisallowCall, mShouldRejectCall, mShouldSilenceCall,
+                    mShouldSkipCallLog, mShouldSkipNotification,
+                    mShouldScreenCallViaAudioProcessing,
+                    mCallComposerAttachmentsToShow);
+        }
+
         public static class Builder {
             private boolean mShouldDisallowCall;
             private boolean mShouldRejectCall;
@@ -244,6 +454,7 @@
             private boolean mShouldSkipCallLog;
             private boolean mShouldSkipNotification;
             private boolean mShouldScreenCallViaAudioProcessing;
+            private int mCallComposerAttachmentsToShow = -1;
 
             /**
              * Sets whether the incoming call should be blocked.
@@ -329,6 +540,38 @@
                 return this;
             }
 
+            /**
+             * Sets the call composer attachments that should be shown to the user.
+             *
+             * Attachments that are not shown will not be passed to the in-call UI responsible for
+             * displaying the call to the user.
+             *
+             * If this method is not called on a {@link Builder}, all attachments will be shown,
+             * except pictures, which will only be shown to users if the call is from a contact.
+             *
+             * Setting attachments to show will have no effect if the call screening service does
+             * not belong to the same package as the system dialer (as returned by
+             * {@link TelecomManager#getSystemDialerPackage()}).
+             *
+             * @param callComposerAttachmentsToShow A bitmask of call composer attachments to show.
+             */
+            public @NonNull Builder setCallComposerAttachmentsToShow(
+                    @CallComposerAttachmentType int callComposerAttachmentsToShow) {
+                // If the argument is less than zero (meaning unset), no-op since the conversion
+                // to/from the parcelable version may call with that value.
+                if (callComposerAttachmentsToShow < 0) {
+                    return this;
+                }
+
+                if ((callComposerAttachmentsToShow
+                        & (1 << NUM_CALL_COMPOSER_ATTACHMENT_TYPES)) != 0) {
+                    throw new IllegalArgumentException("Attachment types must match the ones"
+                            + " defined in CallResponse");
+                }
+                mCallComposerAttachmentsToShow = callComposerAttachmentsToShow;
+                return this;
+            }
+
             public CallResponse build() {
                 return new CallResponse(
                         mShouldDisallowCall,
@@ -336,7 +579,8 @@
                         mShouldSilenceCall,
                         mShouldSkipCallLog,
                         mShouldSkipNotification,
-                        mShouldScreenCallViaAudioProcessing);
+                        mShouldScreenCallViaAudioProcessing,
+                        mCallComposerAttachmentsToShow);
             }
        }
     }
@@ -423,21 +667,12 @@
     public final void respondToCall(@NonNull Call.Details callDetails,
             @NonNull CallResponse response) {
         try {
-            if (response.getDisallowCall()) {
-                mCallScreeningAdapter.disallowCall(
-                        callDetails.getTelecomCallId(),
-                        response.getRejectCall(),
-                        !response.getSkipCallLog(),
-                        !response.getSkipNotification(),
-                        new ComponentName(getPackageName(), getClass().getName()));
-            } else if (response.getSilenceCall()) {
-                mCallScreeningAdapter.silenceCall(callDetails.getTelecomCallId());
-            } else if (response.getShouldScreenCallViaAudioProcessing()) {
-                mCallScreeningAdapter.screenCallFurther(callDetails.getTelecomCallId());
-            } else {
-                mCallScreeningAdapter.allowCall(callDetails.getTelecomCallId());
-            }
+            mCallScreeningAdapter.onScreeningResponse(
+                    callDetails.getTelecomCallId(),
+                    new ComponentName(getPackageName(), getClass().getName()),
+                    response.toParcelable());
         } catch (RemoteException e) {
+            Log.e(this, e, "Got remote exception when returning response");
         }
     }
 }
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 089a948..942a54e 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -3390,11 +3390,20 @@
      *                  {@code true}, {@link #onDisconnect()} will be called soon after
      *                  this is called.
      * @param isInContacts Indicates whether the caller is in the user's contacts list.
+     * @param callScreeningResponse The response that was returned from the
+     *                              {@link CallScreeningService} that handled this call. If no
+     *                              response was received from a call screening service,
+     *                              this will be {@code null}.
+     * @param isResponseFromSystemDialer Whether {@code callScreeningResponse} was sent from the
+     *                                  system dialer. If {@code callScreeningResponse} is
+     *                                  {@code null}, this will be {@code false}.
      * @hide
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.READ_CONTACTS)
-    public void onCallFilteringCompleted(boolean isBlocked, boolean isInContacts) { }
+    public void onCallFilteringCompleted(boolean isBlocked, boolean isInContacts,
+            @Nullable CallScreeningService.CallResponse callScreeningResponse,
+            boolean isResponseFromSystemDialer) { }
 
     static String toLogSafePhoneNumber(String number) {
         // For unknown number, log empty string.
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 170ed3e..966ece3 100755
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -759,6 +759,8 @@
 
         @Override
         public void onCallFilteringCompleted(String callId, boolean isBlocked, boolean isInContacts,
+                CallScreeningService.ParcelableCallResponse callScreeningResponse,
+                boolean isResponseFromSystemDialer,
                 Session.Info sessionInfo) {
             Log.startSession(sessionInfo, SESSION_CALL_FILTERING_COMPLETED);
             try {
@@ -766,7 +768,9 @@
                 args.arg1 = callId;
                 args.arg2 = isBlocked;
                 args.arg3 = isInContacts;
-                args.arg4 = Log.createSubsession();
+                args.arg4 = callScreeningResponse;
+                args.arg5 = isResponseFromSystemDialer;
+                args.arg6 = Log.createSubsession();
                 mHandler.obtainMessage(MSG_ON_CALL_FILTERING_COMPLETED, args).sendToTarget();
             } finally {
                 Log.endSession();
@@ -1437,12 +1441,16 @@
                 case MSG_ON_CALL_FILTERING_COMPLETED: {
                     SomeArgs args = (SomeArgs) msg.obj;
                     try {
-                        Log.continueSession((Session) args.arg4,
+                        Log.continueSession((Session) args.arg6,
                                 SESSION_HANDLER + SESSION_CALL_FILTERING_COMPLETED);
                         String callId = (String) args.arg1;
                         boolean isBlocked = (boolean) args.arg2;
                         boolean isInContacts = (boolean) args.arg3;
-                        onCallFilteringCompleted(callId, isBlocked, isInContacts);
+                        CallScreeningService.ParcelableCallResponse callScreeningResponse =
+                                (CallScreeningService.ParcelableCallResponse) args.arg4;
+                        boolean isResponseFromSystemDialer = (boolean) args.arg5;
+                        onCallFilteringCompleted(callId, isBlocked, isInContacts,
+                                callScreeningResponse, isResponseFromSystemDialer);
                     } finally {
                         args.recycle();
                         Log.endSession();
@@ -2458,11 +2466,16 @@
         }
     }
 
-    private void onCallFilteringCompleted(String callId, boolean isBlocked, boolean isInContacts) {
-        Log.i(this, "onCallFilteringCompleted(%b, %b)", isBlocked, isInContacts);
+    private void onCallFilteringCompleted(String callId, boolean isBlocked, boolean isInContacts,
+            CallScreeningService.ParcelableCallResponse callScreeningResponse,
+            boolean isResponseFromSystemDialer) {
+        Log.i(this, "onCallFilteringCompleted(%s, %b, %b, %s, %b)", callId,
+                isBlocked, isInContacts, callScreeningResponse, isResponseFromSystemDialer);
         Connection connection = findConnectionForAction(callId, "onCallFilteringCompleted");
         if (connection != null) {
-            connection.onCallFilteringCompleted(isBlocked, isInContacts);
+            connection.onCallFilteringCompleted(isBlocked, isInContacts,
+                    callScreeningResponse == null ? null : callScreeningResponse.toCallResponse(),
+                    isResponseFromSystemDialer);
         }
     }
 
diff --git a/telecomm/java/android/telecom/RemoteConnection.java b/telecomm/java/android/telecom/RemoteConnection.java
index feb2ca5..6c6097a 100644
--- a/telecomm/java/android/telecom/RemoteConnection.java
+++ b/telecomm/java/android/telecom/RemoteConnection.java
@@ -1204,15 +1204,25 @@
      * the results of a contacts lookup for the remote party.
      * @param isBlocked Whether call filtering indicates that the call should be blocked
      * @param isInContacts Whether the remote party is in the user's contacts
+     * @param callScreeningResponse The response that was returned from the
+     *                              {@link CallScreeningService} that handled this call. If no
+     *                              response was received from a call screening service,
+     *                              this will be {@code null}.
+     * @param isResponseFromSystemDialer Whether {@code callScreeningResponse} was sent from the
+     *                                  system dialer. If {@code callScreeningResponse} is
+     *                                  {@code null}, this will be {@code false}.
      * @hide
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.READ_CONTACTS)
-    public void onCallFilteringCompleted(boolean isBlocked, boolean isInContacts) {
+    public void onCallFilteringCompleted(boolean isBlocked, boolean isInContacts,
+            @Nullable CallScreeningService.CallResponse callScreeningResponse,
+            boolean isResponseFromSystemDialer) {
         Log.startSession("RC.oCFC", getActiveOwnerInfo());
         try {
             if (mConnected) {
                 mConnectionService.onCallFilteringCompleted(mConnectionId, isBlocked, isInContacts,
+                        callScreeningResponse.toParcelable(), isResponseFromSystemDialer,
                         null /*Session.Info*/);
             }
         } catch (RemoteException ignored) {
diff --git a/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl b/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl
index 83c8f62..0f2e178 100644
--- a/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl
@@ -17,6 +17,7 @@
 package com.android.internal.telecom;
 
 import android.content.ComponentName;
+import android.telecom.CallScreeningService;
 
 /**
  * Internal remote callback interface for call screening services.
@@ -26,16 +27,6 @@
  * {@hide}
  */
 oneway interface ICallScreeningAdapter {
-    void allowCall(String callId);
-
-    void silenceCall(String callId);
-
-    void screenCallFurther(String callId);
-
-    void disallowCall(
-            String callId,
-            boolean shouldReject,
-            boolean shouldAddToCallLog,
-            boolean shouldShowNotification,
-            in ComponentName componentName);
+    void onScreeningResponse(String callId, in ComponentName componentName,
+            in CallScreeningService.ParcelableCallResponse response);
 }
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
index 301c2bb..7599e18 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
@@ -20,6 +20,7 @@
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.telecom.CallAudioState;
+import android.telecom.CallScreeningService;
 import android.telecom.ConnectionRequest;
 import android.telecom.Logging.Session;
 import android.telecom.PhoneAccountHandle;
@@ -119,7 +120,8 @@
     void sendCallEvent(String callId, String event, in Bundle extras, in Session.Info sessionInfo);
 
     void onCallFilteringCompleted(String callId, boolean isBlocked, boolean isInContacts,
-            in Session.Info sessionInfo);
+            in CallScreeningService.ParcelableCallResponse callScreeningResponse,
+            boolean isResponseFromSystemDialer, in Session.Info sessionInfo);
 
     void onExtrasChanged(String callId, in Bundle extras, in Session.Info sessionInfo);
 
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 3f6162d..6d3fa81 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -4601,8 +4601,9 @@
             "mmi_two_digit_number_pattern_string_array";
 
     /**
-     * Holds the list of carrier certificate hashes.
-     * Note that each carrier has its own certificates.
+     * Holds the list of carrier certificate hashes, followed by optional package names.
+     * Format: "sha1/256" or "sha1/256:package1,package2,package3..."
+     * Note that each carrier has its own hashes.
      */
     public static final String KEY_CARRIER_CERTIFICATE_STRING_ARRAY =
             "carrier_certificate_string_array";
@@ -4763,6 +4764,14 @@
     public static final String KEY_STORE_SIM_PIN_FOR_UNATTENDED_REBOOT_BOOL =
             "store_sim_pin_for_unattended_reboot_bool";
 
+     /**
+     * Determine whether "Enable 2G" toggle can be shown.
+     *
+     * Used to trade privacy/security against potentially reduced carrier coverage for some
+     * carriers.
+     */
+    public static final String KEY_HIDE_ENABLE_2G = "hide_enable_2g_bool";
+
     /** The default value for every variable. */
     private final static PersistableBundle sDefaults;
 
@@ -5320,6 +5329,7 @@
         sDefaults.putBoolean(KEY_NETWORK_TEMP_NOT_METERED_SUPPORTED_BOOL, true);
         sDefaults.putInt(KEY_DEFAULT_RTT_MODE_INT, 0);
         sDefaults.putBoolean(KEY_STORE_SIM_PIN_FOR_UNATTENDED_REBOOT_BOOL, true);
+        sDefaults.putBoolean(KEY_HIDE_ENABLE_2G, false);
     }
 
     /**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index ee3a0ef..6a9b71d 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -4461,6 +4461,10 @@
      * This functionality is only available to the app filling the {@link RoleManager#ROLE_DIALER}
      * role on the device.
      *
+     * This functionality is only available when
+     * {@link CarrierConfigManager#KEY_SUPPORTS_CALL_COMPOSER_BOOL} is set to {@code true} in the
+     * bundle returned from {@link #getCarrierConfig()}.
+     *
      * @param pictureToUpload An {@link InputStream} that supplies the bytes representing the
      *                        picture to upload. The client bears responsibility for closing this
      *                        stream after {@code callback} is called with success or failure.
@@ -4469,7 +4473,9 @@
      *                        of {@link #getMaximumCallComposerPictureSize()}, the upload will be
      *                        aborted and the callback will be called with an exception containing
      *                        {@link CallComposerException#ERROR_FILE_TOO_LARGE}.
-     * @param contentType The MIME type of the picture you're uploading (e.g. image/jpeg)
+     * @param contentType The MIME type of the picture you're uploading (e.g. image/jpeg). The list
+     *                    of acceptable content types can be found at 3GPP TS 26.141 sections
+     *                    4.2 and 4.3.
      * @param executor The {@link Executor} on which the {@code pictureToUpload} stream will be
      *                 read, as well as on which the callback will be called.
      * @param callback A callback called when the upload operation terminates, either in success
@@ -8591,7 +8597,8 @@
     @IntDef({
             ALLOWED_NETWORK_TYPES_REASON_USER,
             ALLOWED_NETWORK_TYPES_REASON_POWER,
-            ALLOWED_NETWORK_TYPES_REASON_CARRIER
+            ALLOWED_NETWORK_TYPES_REASON_CARRIER,
+            ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface AllowedNetworkTypesReason {
@@ -8628,6 +8635,14 @@
     public static final int ALLOWED_NETWORK_TYPES_REASON_CARRIER = 2;
 
     /**
+     * To indicate allowed network type change is requested by the user via the 2G toggle.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G = 3;
+
+    /**
      * Set the allowed network types of the device and
      * provide the reason triggering the allowed network change.
      * This can be called for following reasons
@@ -8636,6 +8651,8 @@
      * <li>Allowed network types control by power manager
      * {@link #ALLOWED_NETWORK_TYPES_REASON_POWER}
      * <li>Allowed network types control by carrier {@link #ALLOWED_NETWORK_TYPES_REASON_CARRIER}
+     * <li>Allowed network types control by the user-controlled "Allow 2G" toggle
+     * {@link #ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G}
      * </ol>
      * This API will result in allowing an intersection of allowed network types for all reasons,
      * including the configuration done through other reasons.
@@ -8719,6 +8736,7 @@
             case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER:
             case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_POWER:
             case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_CARRIER:
+            case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G:
                 return true;
         }
         return false;
diff --git a/telephony/java/android/telephony/UiccAccessRule.java b/telephony/java/android/telephony/UiccAccessRule.java
index 7773c0a..38b551b 100644
--- a/telephony/java/android/telephony/UiccAccessRule.java
+++ b/telephony/java/android/telephony/UiccAccessRule.java
@@ -35,6 +35,7 @@
 import java.io.IOException;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
@@ -52,6 +53,16 @@
 
     private static final int ENCODING_VERSION = 1;
 
+    /**
+     * Delimiter used to decode {@link CarrierConfigManager#KEY_CARRIER_CERTIFICATE_STRING_ARRAY}.
+     */
+    private static final String DELIMITER_CERTIFICATE_HASH_PACKAGE_NAMES = ":";
+
+    /**
+     * Delimiter used to decode {@link CarrierConfigManager#KEY_CARRIER_CERTIFICATE_STRING_ARRAY}.
+     */
+    private static final String DELIMITER_INDIVIDUAL_PACKAGE_NAMES = ",";
+
     public static final @android.annotation.NonNull Creator<UiccAccessRule> CREATOR = new Creator<UiccAccessRule>() {
         @Override
         public UiccAccessRule createFromParcel(Parcel in) {
@@ -98,6 +109,36 @@
     }
 
     /**
+     * Decodes {@link CarrierConfigManager#KEY_CARRIER_CERTIFICATE_STRING_ARRAY} values.
+     * @hide
+     */
+    @Nullable
+    public static UiccAccessRule[] decodeRulesFromCarrierConfig(@Nullable String[] certs) {
+        if (certs == null) {
+            return null;
+        }
+        List<UiccAccessRule> carrierConfigAccessRulesArray = new ArrayList();
+        for (String cert : certs) {
+            String[] splitStr = cert.split(DELIMITER_CERTIFICATE_HASH_PACKAGE_NAMES);
+            byte[] certificateHash = IccUtils.hexStringToBytes(splitStr[0]);
+            if (splitStr.length == 1) {
+                // The value is a certificate hash, without any package name
+                carrierConfigAccessRulesArray.add(new UiccAccessRule(certificateHash, null, 0));
+            } else {
+                // The value is composed of the certificate hash followed by at least one
+                // package name
+                String[] packageNames = splitStr[1].split(DELIMITER_INDIVIDUAL_PACKAGE_NAMES);
+                for (String packageName : packageNames) {
+                    carrierConfigAccessRulesArray.add(
+                            new UiccAccessRule(certificateHash, packageName, 0));
+                }
+            }
+        }
+        return carrierConfigAccessRulesArray.toArray(
+            new UiccAccessRule[carrierConfigAccessRulesArray.size()]);
+    }
+
+    /**
      * Decodes a byte array generated with {@link #encodeRules}.
      * @hide
      */
@@ -222,6 +263,14 @@
         return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
     }
 
+    /**
+     * Returns true if the given certificate and package name match this rule's values.
+     * @hide
+     */
+    public boolean matches(@Nullable String certHash, @Nullable String packageName) {
+        return matches(IccUtils.hexStringToBytes(certHash), packageName);
+    }
+
     private boolean matches(byte[] certHash, String packageName) {
         return certHash != null && Arrays.equals(this.mCertificateHash, certHash) &&
                 (TextUtils.isEmpty(this.mPackageName) || this.mPackageName.equals(packageName));
diff --git a/telephony/java/android/telephony/ims/RcsUceAdapter.java b/telephony/java/android/telephony/ims/RcsUceAdapter.java
index 070fd799..09c07d3 100644
--- a/telephony/java/android/telephony/ims/RcsUceAdapter.java
+++ b/telephony/java/android/telephony/ims/RcsUceAdapter.java
@@ -32,6 +32,7 @@
 import android.telephony.ims.aidl.IImsRcsController;
 import android.telephony.ims.aidl.IRcsUceControllerCallback;
 import android.telephony.ims.aidl.IRcsUcePublishStateCallback;
+import android.telephony.ims.feature.RcsFeature;
 import android.util.Log;
 
 import java.lang.annotation.Retention;
@@ -467,7 +468,7 @@
      * poll on the network unless there are contacts being queried with stale information.
      * <p>
      * Be sure to check the availability of this feature using
-     * {@link ImsRcsManager#isAvailable(int)} and ensuring
+     * {@link ImsRcsManager#isAvailable(int, int)} and ensuring
      * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE} or
      * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE} is enabled or else
      * this operation will fail with {@link #ERROR_NOT_AVAILABLE} or {@link #ERROR_NOT_ENABLED}.
@@ -484,7 +485,8 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @RequiresPermission(allOf = {Manifest.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE,
+            Manifest.permission.READ_CONTACTS})
     public void requestCapabilities(@NonNull List<Uri> contactNumbers,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull CapabilitiesCallback c) throws ImsException {
@@ -557,7 +559,7 @@
      *
      * <p>
      * Be sure to check the availability of this feature using
-     * {@link ImsRcsManager#isAvailable(int)} and ensuring
+     * {@link ImsRcsManager#isAvailable(int, int)} and ensuring
      * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE} or
      * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE} is
      * enabled or else this operation will fail with
@@ -571,7 +573,8 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+    @RequiresPermission(allOf = {Manifest.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE,
+            Manifest.permission.READ_CONTACTS})
     public void requestAvailability(@NonNull Uri contactNumber,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull CapabilitiesCallback c) throws ImsException {
diff --git a/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java b/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java
index 4435640e..a217d13 100644
--- a/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java
+++ b/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java
@@ -21,6 +21,7 @@
 import android.net.Uri;
 import android.os.Binder;
 import android.os.RemoteException;
+import android.telephony.ims.ImsException;
 import android.telephony.ims.RcsContactUceCapability;
 import android.telephony.ims.stub.CapabilityExchangeEventListener;
 import android.util.Log;
@@ -47,7 +48,7 @@
      * Receives the request of publishing capabilities from the network and deliver this request
      * to the framework via the registered capability exchange event listener.
      */
-    public void onRequestPublishCapabilities(int publishTriggerType) {
+    public void onRequestPublishCapabilities(int publishTriggerType) throws ImsException {
         ICapabilityExchangeEventListener listener = mListenerBinder;
         if (listener == null) {
             return;
@@ -56,13 +57,15 @@
             listener.onRequestPublishCapabilities(publishTriggerType);
         } catch (RemoteException e) {
             Log.w(LOG_TAG, "request publish capabilities exception: " + e);
+            throw new ImsException("Remote is not available",
+                    ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
         }
     }
 
     /**
      * Receives the unpublish notification and deliver this callback to the framework.
      */
-    public void onUnpublish() {
+    public void onUnpublish() throws ImsException {
         ICapabilityExchangeEventListener listener = mListenerBinder;
         if (listener == null) {
             return;
@@ -71,6 +74,8 @@
             listener.onUnpublish();
         } catch (RemoteException e) {
             Log.w(LOG_TAG, "Unpublish exception: " + e);
+            throw new ImsException("Remote is not available",
+                    ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
         }
     }
 
@@ -79,7 +84,8 @@
      * request to the framework.
      */
     public void onRemoteCapabilityRequest(@NonNull Uri contactUri,
-            @NonNull List<String> remoteCapabilities, @NonNull OptionsRequestCallback callback) {
+            @NonNull List<String> remoteCapabilities, @NonNull OptionsRequestCallback callback)
+            throws ImsException {
         ICapabilityExchangeEventListener listener = mListenerBinder;
         if (listener == null) {
             return;
@@ -87,10 +93,11 @@
 
         IOptionsRequestCallback internalCallback = new IOptionsRequestCallback.Stub() {
             @Override
-            public void respondToCapabilityRequest(RcsContactUceCapability ownCapabilities) {
+            public void respondToCapabilityRequest(RcsContactUceCapability ownCapabilities,
+                    boolean isBlocked) {
                 final long callingIdentity = Binder.clearCallingIdentity();
                 try {
-                    callback.onRespondToCapabilityRequest(ownCapabilities);
+                    callback.onRespondToCapabilityRequest(ownCapabilities, isBlocked);
                 } finally {
                     restoreCallingIdentity(callingIdentity);
                 }
@@ -110,6 +117,8 @@
             listener.onRemoteCapabilityRequest(contactUri, remoteCapabilities, internalCallback);
         } catch (RemoteException e) {
             Log.w(LOG_TAG, "Remote capability request exception: " + e);
+            throw new ImsException("Remote is not available",
+                    ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
         }
     }
 }
diff --git a/telephony/java/android/telephony/ims/aidl/IOptionsRequestCallback.aidl b/telephony/java/android/telephony/ims/aidl/IOptionsRequestCallback.aidl
index d4d5301..8eecbca 100644
--- a/telephony/java/android/telephony/ims/aidl/IOptionsRequestCallback.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IOptionsRequestCallback.aidl
@@ -27,8 +27,9 @@
      * Respond to a remote capability request from the contact specified with the capabilities
      * of this device.
      * @param ownCapabilities The capabilities of this device.
+     * @param isBlocked True if the user has blocked the number sending this request.
      */
-    void respondToCapabilityRequest(in RcsContactUceCapability ownCapabilities);
+    void respondToCapabilityRequest(in RcsContactUceCapability ownCapabilities, boolean isBlocked);
 
     /**
      * Respond to a remote capability request from the contact specified with the
diff --git a/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java b/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java
index d9734a7..4967e5d 100644
--- a/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java
+++ b/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java
@@ -16,32 +16,58 @@
 
 package android.telephony.ims.stub;
 
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.net.Uri;
 import android.telephony.ims.ImsException;
 import android.telephony.ims.RcsContactUceCapability;
 import android.telephony.ims.RcsUceAdapter;
 import android.telephony.ims.feature.ImsFeature;
 import android.telephony.ims.feature.RcsFeature;
+import android.util.Log;
+
+import java.util.List;
 
 /**
- * The interface of the capabilities event listener for ImsService to notify the framework of the
- * UCE request and status updated.
+ * The interface that is used by the framework to listen to events from the vendor RCS stack
+ * regarding capabilities exchange using presence server and OPTIONS.
  * @hide
  */
 @SystemApi
 public interface CapabilityExchangeEventListener {
     /**
      * Interface used by the framework to respond to OPTIONS requests.
-     * @hide
      */
     interface OptionsRequestCallback {
         /**
          * Respond to a remote capability request from the contact specified with the
          * capabilities of this device.
          * @param ownCapabilities The capabilities of this device.
+         * @hide
          */
-        void onRespondToCapabilityRequest(@NonNull RcsContactUceCapability ownCapabilities);
+        default void onRespondToCapabilityRequest(
+                @NonNull RcsContactUceCapability ownCapabilities) {}
+
+        /**
+         * Respond to a remote capability request from the contact specified with the
+         * capabilities of this device.
+         * @param ownCapabilities The capabilities of this device.
+         * @param isBlocked Whether or not the user has blocked the number requesting the
+         *         capabilities of this device. If true, the device should respond to the OPTIONS
+         *         request with a 200 OK response and no capabilities.
+         */
+        default void onRespondToCapabilityRequest(@NonNull RcsContactUceCapability ownCapabilities,
+                boolean isBlocked) {
+            Log.w("CapabilityExchangeEventListener", "implement "
+                    + "onRespondToCapabilityRequest(RcsContactUceCapability, boolean) instead!");
+            // Fall back to old implementation
+            if (isBlocked) {
+                onRespondToCapabilityRequestWithError(200, "OK");
+            } else {
+                onRespondToCapabilityRequest(ownCapabilities);
+            }
+        }
 
         /**
          * Respond to a remote capability request from the contact specified with the
@@ -49,7 +75,8 @@
          * @param code The SIP response code to respond with.
          * @param reason A non-null String containing the reason associated with the SIP code.
          */
-        void onRespondToCapabilityRequestWithError(int code, @NonNull String reason);
+        void onRespondToCapabilityRequestWithError(@IntRange(from = 100, to = 699) int code,
+                @NonNull String reason);
     }
 
     /**
@@ -59,8 +86,7 @@
      * This is typically used when trying to generate an initial PUBLISH for a new subscription to
      * the network. The device will cache all presence publications after boot until this method is
      * called the first time.
-     * @param publishTriggerType {@link RcsUceAdapter#StackPublishTriggerType} The reason for the
-     * capability update request.
+     * @param publishTriggerType The reason for the capability update request.
      * @throws ImsException If this {@link RcsCapabilityExchangeImplBase} instance is not currently
      * connected to the framework. This can happen if the {@link RcsFeature} is not
      * {@link ImsFeature#STATE_READY} and the {@link RcsFeature} has not received the
@@ -81,4 +107,25 @@
      * Telephony stack has crashed.
      */
     void onUnpublish() throws ImsException;
+
+    /**
+     * Inform the framework of an OPTIONS query from a remote device for this device's UCE
+     * capabilities.
+     * <p>
+     * The framework will respond via the
+     * {@link OptionsRequestCallback#onRespondToCapabilityRequest} or
+     * {@link OptionsRequestCallback#onRespondToCapabilityRequestWithError}.
+     * @param contactUri The URI associated with the remote contact that is
+     * requesting capabilities.
+     * @param remoteCapabilities The remote contact's capability information.
+     * @param callback The callback of this request which is sent from the remote user.
+     * @throws ImsException If this {@link RcsCapabilityExchangeImplBase} instance is not
+     * currently connected to the framework. This can happen if the {@link RcsFeature} is not
+     * {@link ImsFeature#STATE_READY} and the {@link RcsFeature} has not received
+     * the {@link ImsFeature#onFeatureReady()} callback. This may also happen in rare
+     * cases when the Telephony stack has crashed.
+     */
+    void onRemoteCapabilityRequest(@NonNull Uri contactUri,
+            @NonNull List<String> remoteCapabilities,
+            @NonNull OptionsRequestCallback callback) throws ImsException;
 }
diff --git a/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java b/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java
index ec98be6..908869b 100644
--- a/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java
@@ -19,7 +19,6 @@
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.net.Uri;
@@ -141,7 +140,7 @@
          * {@link #publishCapabilities(String, PublishResponseCallback)}.
          *
          * If this network response also contains a “Reason” header, then the
-         * {@link onNetworkResponse(int, String, int, String)} method should be used instead.
+         * {@link #onNetworkResponse(int, String, int, String)} method should be used instead.
          *
          * @param sipCode The SIP response code sent from the network for the operation
          * token specified.
@@ -160,7 +159,7 @@
 
         /**
          * Provide the framework with a subsequent network response update to
-         * {@link #publishCapabilities(RcsContactUceCapability, int)} that also
+         * {@link #publishCapabilities(String, PublishResponseCallback)} that also
          * includes a reason provided in the “reason” header. See RFC3326 for more
          * information.
          *
@@ -186,7 +185,6 @@
 
     /**
      * Interface used by the framework to respond to OPTIONS requests.
-     * @hide
      */
     public interface OptionsResponseCallback {
         /**
@@ -217,7 +215,7 @@
          * cases when the Telephony stack has crashed.
          */
         void onNetworkResponse(int sipCode, @NonNull String reason,
-                @Nullable List<String> theirCaps) throws ImsException;
+                @NonNull List<String> theirCaps) throws ImsException;
     }
 
     /**
@@ -243,7 +241,7 @@
 
         /**
          * Notify the framework of the response to the SUBSCRIBE request from
-         * {@link #subscribeForCapabilities(List<Uri>, SubscribeResponseCallback)}.
+         * {@link #subscribeForCapabilities(List, SubscribeResponseCallback)}.
          * <p>
          * If the carrier network responds to the SUBSCRIBE request with a 2XX response, then the
          * framework will expect the IMS stack to call {@link #onNotifyCapabilitiesUpdate},
@@ -251,7 +249,7 @@
          * subsequent NOTIFY responses to the subscription.
          *
          * If this network response also contains a “Reason” header, then the
-         * {@link onNetworkResponse(int, String, int, String)} method should be used instead.
+         * {@link #onNetworkResponse(int, String, int, String)} method should be used instead.
          *
          * @param sipCode The SIP response code sent from the network for the operation
          * token specified.
@@ -268,7 +266,7 @@
 
         /**
          * Notify the framework  of the response to the SUBSCRIBE request from
-         * {@link #subscribeForCapabilities(RcsContactUceCapability, int)} that also
+         * {@link #subscribeForCapabilities(List, SubscribeResponseCallback)} that also
          * includes a reason provided in the “reason” header. See RFC3326 for more
          * information.
          *
@@ -294,7 +292,8 @@
         /**
          * Notify the framework of the latest XML PIDF documents included in the network response
          * for the requested contacts' capabilities requested by the Framework using
-         * {@link RcsUceAdapter#requestCapabilities(Executor, List<Uri>, CapabilitiesCallback)}.
+         * {@link RcsUceAdapter#requestCapabilities(List, Executor,
+         * RcsUceAdapter.CapabilitiesCallback)}.
          * <p>
          * The expected format for the PIDF XML is defined in RFC3861. Each XML document must be a
          * "application/pidf+xml" object and start with a root <presence> element. For NOTIFY
@@ -336,7 +335,8 @@
 
         /**
          * The subscription associated with a previous
-         * {@link RcsUceAdapter#requestCapabilities(Executor, List<Uri>, CapabilitiesCallback)}
+         * {@link RcsUceAdapter#requestCapabilities(List, Executor,
+         * RcsUceAdapter.CapabilitiesCallback)}
          * operation has been terminated. This will mostly be due to the network sending a final
          * NOTIFY response due to the subscription expiring, but this may also happen due to a
          * network error.
@@ -427,12 +427,11 @@
      * Push one's own capabilities to a remote user via the SIP OPTIONS presence exchange mechanism
      * in order to receive the capabilities of the remote user in response.
      * <p>
-     * The implementer must call {@link #onNetworkResponse} to send the response of this
-     * query back to the framework.
+     * The implementer must use {@link OptionsResponseCallback} to send the response of
+     * this query from the network back to the framework.
      * @param contactUri The URI of the remote user that we wish to get the capabilities of.
      * @param myCapabilities The capabilities of this device to send to the remote user.
      * @param callback The callback of this request which is sent from the remote user.
-     * @hide
      */
     // executor used is defined in the constructor.
     @SuppressLint("ExecutorRegistration")
diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java
index 6fbde50..15d19a4 100644
--- a/telephony/java/com/android/internal/telephony/DctConstants.java
+++ b/telephony/java/com/android/internal/telephony/DctConstants.java
@@ -125,6 +125,7 @@
     public static final String PROVISIONING_URL_KEY = "provisioningUrl";
     public static final String BANDWIDTH_SOURCE_MODEM_KEY = "modem";
     public static final String BANDWIDTH_SOURCE_CARRIER_CONFIG_KEY = "carrier_config";
+    public static final String BANDWIDTH_SOURCE_BANDWIDTH_ESTIMATOR_KEY = "bandwidth_estimator";
     public static final String RAT_NAME_LTE = "LTE";
     public static final String RAT_NAME_NR_NSA = "NR_NSA";
     public static final String RAT_NAME_NR_NSA_MMWAVE = "NR_NSA_MMWAVE";
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 2a693eb..1358410 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -66,6 +66,8 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_RCS;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_SUPL;
@@ -169,6 +171,7 @@
 import android.net.INetworkMonitorCallbacks;
 import android.net.INetworkPolicyListener;
 import android.net.INetworkStatsService;
+import android.net.IOnSetOemNetworkPreferenceListener;
 import android.net.IQosCallback;
 import android.net.InetAddresses;
 import android.net.InterfaceConfigurationParcel;
@@ -192,6 +195,7 @@
 import android.net.NetworkStackClient;
 import android.net.NetworkState;
 import android.net.NetworkTestResultParcelable;
+import android.net.OemNetworkPreferences;
 import android.net.ProxyInfo;
 import android.net.QosCallbackException;
 import android.net.QosFilter;
@@ -300,6 +304,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -359,6 +364,7 @@
     private static final String WIFI_WOL_IFNAME = "test_wlan_wol";
     private static final String VPN_IFNAME = "tun10042";
     private static final String TEST_PACKAGE_NAME = "com.android.test.package";
+    private static final int TEST_PACKAGE_UID = 123;
     private static final String ALWAYS_ON_PACKAGE = "com.android.test.alwaysonvpn";
 
     private static final String INTERFACE_NAME = "interface";
@@ -418,6 +424,7 @@
     @Mock EthernetManager mEthernetManager;
     @Mock NetworkPolicyManager mNetworkPolicyManager;
     @Mock KeyStore mKeyStore;
+    @Mock IOnSetOemNetworkPreferenceListener mOnSetOemNetworkPreferenceListener;
 
     private ArgumentCaptor<ResolverParamsParcel> mResolverParamsParcelCaptor =
             ArgumentCaptor.forClass(ResolverParamsParcel.class);
@@ -9422,4 +9429,264 @@
         }
         fail("TOO_MANY_REQUESTS never thrown");
     }
+
+    private void mockGetApplicationInfo(@NonNull final String packageName, @NonNull final int uid)
+            throws PackageManager.NameNotFoundException {
+        final ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.uid = uid;
+        when(mPackageManager.getApplicationInfo(eq(packageName), anyInt()))
+                .thenReturn(applicationInfo);
+    }
+
+    private void mockHasSystemFeature(@NonNull final String featureName,
+            @NonNull final boolean hasFeature) {
+        when(mPackageManager.hasSystemFeature(eq(featureName)))
+                .thenReturn(hasFeature);
+    }
+
+    private UidRange getNriFirstUidRange(
+            @NonNull final ConnectivityService.NetworkRequestInfo nri) {
+        return nri.mRequests.get(0).networkCapabilities.getUids().iterator().next();
+    }
+
+    private OemNetworkPreferences createDefaultOemNetworkPreferences(
+            @OemNetworkPreferences.OemNetworkPreference final int preference)
+            throws PackageManager.NameNotFoundException {
+        // Arrange PackageManager mocks
+        mockGetApplicationInfo(TEST_PACKAGE_NAME, TEST_PACKAGE_UID);
+
+        // Build OemNetworkPreferences object
+        return new OemNetworkPreferences.Builder()
+                .addNetworkPreference(TEST_PACKAGE_NAME, preference)
+                .build();
+    }
+
+    @Test
+    public void testOemNetworkRequestFactoryPreferenceUninitializedThrowsError()
+            throws PackageManager.NameNotFoundException {
+        @OemNetworkPreferences.OemNetworkPreference final int prefToTest =
+                OemNetworkPreferences.OEM_NETWORK_PREFERENCE_UNINITIALIZED;
+
+        // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
+        assertThrows(IllegalArgumentException.class,
+                () -> mService.new OemNetworkRequestFactory()
+                        .createNrisFromOemNetworkPreferences(
+                                createDefaultOemNetworkPreferences(prefToTest)));
+    }
+
+    @Test
+    public void testOemNetworkRequestFactoryPreferenceOemPaid()
+            throws PackageManager.NameNotFoundException {
+        // Expectations
+        final int expectedNumOfNris = 1;
+        final int expectedNumOfRequests = 3;
+
+        @OemNetworkPreferences.OemNetworkPreference final int prefToTest =
+                OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID;
+
+        // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
+        final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+                mService.new OemNetworkRequestFactory()
+                        .createNrisFromOemNetworkPreferences(
+                                createDefaultOemNetworkPreferences(prefToTest));
+
+        final List<NetworkRequest> mRequests = nris.iterator().next().mRequests;
+        assertEquals(expectedNumOfNris, nris.size());
+        assertEquals(expectedNumOfRequests, mRequests.size());
+        assertTrue(mRequests.get(0).isListen());
+        assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_NOT_METERED));
+        assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_VALIDATED));
+        assertTrue(mRequests.get(1).isRequest());
+        assertTrue(mRequests.get(1).hasCapability(NET_CAPABILITY_OEM_PAID));
+        assertTrue(mRequests.get(2).isRequest());
+        assertTrue(mService.getDefaultRequest().networkCapabilities.equalsNetCapabilities(
+                mRequests.get(2).networkCapabilities));
+    }
+
+    @Test
+    public void testOemNetworkRequestFactoryPreferenceOemPaidNoFallback()
+            throws PackageManager.NameNotFoundException {
+        // Expectations
+        final int expectedNumOfNris = 1;
+        final int expectedNumOfRequests = 2;
+
+        @OemNetworkPreferences.OemNetworkPreference final int prefToTest =
+                OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK;
+
+        // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
+        final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+                mService.new OemNetworkRequestFactory()
+                        .createNrisFromOemNetworkPreferences(
+                                createDefaultOemNetworkPreferences(prefToTest));
+
+        final List<NetworkRequest> mRequests = nris.iterator().next().mRequests;
+        assertEquals(expectedNumOfNris, nris.size());
+        assertEquals(expectedNumOfRequests, mRequests.size());
+        assertTrue(mRequests.get(0).isListen());
+        assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_NOT_METERED));
+        assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_VALIDATED));
+        assertTrue(mRequests.get(1).isRequest());
+        assertTrue(mRequests.get(1).hasCapability(NET_CAPABILITY_OEM_PAID));
+    }
+
+    @Test
+    public void testOemNetworkRequestFactoryPreferenceOemPaidOnly()
+            throws PackageManager.NameNotFoundException {
+        // Expectations
+        final int expectedNumOfNris = 1;
+        final int expectedNumOfRequests = 1;
+
+        @OemNetworkPreferences.OemNetworkPreference final int prefToTest =
+                OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY;
+
+        // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
+        final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+                mService.new OemNetworkRequestFactory()
+                        .createNrisFromOemNetworkPreferences(
+                                createDefaultOemNetworkPreferences(prefToTest));
+
+        final List<NetworkRequest> mRequests = nris.iterator().next().mRequests;
+        assertEquals(expectedNumOfNris, nris.size());
+        assertEquals(expectedNumOfRequests, mRequests.size());
+        assertTrue(mRequests.get(0).isRequest());
+        assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_OEM_PAID));
+    }
+
+    @Test
+    public void testOemNetworkRequestFactoryPreferenceOemPrivateOnly()
+            throws PackageManager.NameNotFoundException {
+        // Expectations
+        final int expectedNumOfNris = 1;
+        final int expectedNumOfRequests = 1;
+
+        @OemNetworkPreferences.OemNetworkPreference final int prefToTest =
+                OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY;
+
+        // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
+        final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+                mService.new OemNetworkRequestFactory()
+                        .createNrisFromOemNetworkPreferences(
+                                createDefaultOemNetworkPreferences(prefToTest));
+
+        final List<NetworkRequest> mRequests = nris.iterator().next().mRequests;
+        assertEquals(expectedNumOfNris, nris.size());
+        assertEquals(expectedNumOfRequests, mRequests.size());
+        assertTrue(mRequests.get(0).isRequest());
+        assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_OEM_PRIVATE));
+        assertFalse(mRequests.get(0).hasCapability(NET_CAPABILITY_OEM_PAID));
+    }
+
+    @Test
+    public void testOemNetworkRequestFactoryCreatesCorrectNumOfNris()
+            throws PackageManager.NameNotFoundException {
+        // Expectations
+        final int expectedNumOfNris = 2;
+
+        // Arrange PackageManager mocks
+        final String testPackageName2 = "com.google.apps.dialer";
+        mockGetApplicationInfo(TEST_PACKAGE_NAME, TEST_PACKAGE_UID);
+        mockGetApplicationInfo(testPackageName2, TEST_PACKAGE_UID);
+
+        // Build OemNetworkPreferences object
+        final int testOemPref = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID;
+        final int testOemPref2 = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK;
+        final OemNetworkPreferences pref = new OemNetworkPreferences.Builder()
+                .addNetworkPreference(TEST_PACKAGE_NAME, testOemPref)
+                .addNetworkPreference(testPackageName2, testOemPref2)
+                .build();
+
+        // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
+        final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+                mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(pref);
+
+        assertNotNull(nris);
+        assertEquals(expectedNumOfNris, nris.size());
+    }
+
+    @Test
+    public void testOemNetworkRequestFactoryCorrectlySetsUids()
+            throws PackageManager.NameNotFoundException {
+        // Arrange PackageManager mocks
+        final String testPackageName2 = "com.google.apps.dialer";
+        final int testPackageNameUid2 = 456;
+        mockGetApplicationInfo(TEST_PACKAGE_NAME, TEST_PACKAGE_UID);
+        mockGetApplicationInfo(testPackageName2, testPackageNameUid2);
+
+        // Build OemNetworkPreferences object
+        final int testOemPref = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID;
+        final int testOemPref2 = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK;
+        final OemNetworkPreferences pref = new OemNetworkPreferences.Builder()
+                .addNetworkPreference(TEST_PACKAGE_NAME, testOemPref)
+                .addNetworkPreference(testPackageName2, testOemPref2)
+                .build();
+
+        // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
+        final List<ConnectivityService.NetworkRequestInfo> nris =
+                new ArrayList<>(
+                        mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(
+                                pref));
+
+        // Sort by uid to access nris by index
+        nris.sort(Comparator.comparingInt(nri -> getNriFirstUidRange(nri).start));
+        assertEquals(TEST_PACKAGE_UID, getNriFirstUidRange(nris.get(0)).start);
+        assertEquals(TEST_PACKAGE_UID, getNriFirstUidRange(nris.get(0)).stop);
+        assertEquals(testPackageNameUid2, getNriFirstUidRange(nris.get(1)).start);
+        assertEquals(testPackageNameUid2, getNriFirstUidRange(nris.get(1)).stop);
+    }
+
+    @Test
+    public void testOemNetworkRequestFactoryAddsPackagesToCorrectPreference()
+            throws PackageManager.NameNotFoundException {
+        // Expectations
+        final int expectedNumOfNris = 1;
+        final int expectedNumOfAppUids = 2;
+
+        // Arrange PackageManager mocks
+        final String testPackageName2 = "com.google.apps.dialer";
+        final int testPackageNameUid2 = 456;
+        mockGetApplicationInfo(TEST_PACKAGE_NAME, TEST_PACKAGE_UID);
+        mockGetApplicationInfo(testPackageName2, testPackageNameUid2);
+
+        // Build OemNetworkPreferences object
+        final int testOemPref = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID;
+        final OemNetworkPreferences pref = new OemNetworkPreferences.Builder()
+                .addNetworkPreference(TEST_PACKAGE_NAME, testOemPref)
+                .addNetworkPreference(testPackageName2, testOemPref)
+                .build();
+
+        // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
+        final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+                mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(pref);
+
+        assertEquals(expectedNumOfNris, nris.size());
+        assertEquals(expectedNumOfAppUids,
+                nris.iterator().next().mRequests.get(0).networkCapabilities.getUids().size());
+    }
+
+    @Test
+    public void testSetOemNetworkPreferenceNullListenerAndPrefParamThrowsNpe() {
+        mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, true);
+        @OemNetworkPreferences.OemNetworkPreference final int networkPref =
+                OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY;
+
+        // Act on ConnectivityService.setOemNetworkPreference()
+        assertThrows(NullPointerException.class,
+                () -> mService.setOemNetworkPreference(
+                        null,
+                        null));
+    }
+
+    @Test
+    public void testSetOemNetworkPreferenceFailsForNonAutomotive()
+            throws PackageManager.NameNotFoundException, RemoteException {
+        mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, false);
+        @OemNetworkPreferences.OemNetworkPreference final int networkPref =
+                OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY;
+
+        // Act on ConnectivityService.setOemNetworkPreference()
+        assertThrows(UnsupportedOperationException.class,
+                () -> mService.setOemNetworkPreference(
+                        createDefaultOemNetworkPreferences(networkPref),
+                        mOnSetOemNetworkPreferenceListener));
+    }
 }
diff --git a/tests/vcn/Android.bp b/tests/vcn/Android.bp
index c04ddd7..1dedc19 100644
--- a/tests/vcn/Android.bp
+++ b/tests/vcn/Android.bp
@@ -21,7 +21,6 @@
         "services.core",
     ],
     libs: [
-        "android.net.ipsec.ike.stubs.module_lib",
         "android.test.runner",
         "android.test.base",
         "android.test.mock",
diff --git a/tests/vcn/java/android/net/vcn/VcnControlPlaneIkeConfigTest.java b/tests/vcn/java/android/net/vcn/VcnControlPlaneIkeConfigTest.java
new file mode 100644
index 0000000..36f5e41
--- /dev/null
+++ b/tests/vcn/java/android/net/vcn/VcnControlPlaneIkeConfigTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vcn;
+
+import static android.net.ipsec.ike.SaProposal.DH_GROUP_2048_BIT_MODP;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12;
+import static android.net.ipsec.ike.SaProposal.PSEUDORANDOM_FUNCTION_AES128_XCBC;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.ipsec.ike.ChildSaProposal;
+import android.net.ipsec.ike.IkeFqdnIdentification;
+import android.net.ipsec.ike.IkeSaProposal;
+import android.net.ipsec.ike.IkeSessionParams;
+import android.net.ipsec.ike.SaProposal;
+import android.net.ipsec.ike.TunnelModeChildSessionParams;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class VcnControlPlaneIkeConfigTest {
+    private static final IkeSessionParams IKE_PARAMS;
+    private static final TunnelModeChildSessionParams CHILD_PARAMS;
+
+    static {
+        IkeSaProposal ikeProposal =
+                new IkeSaProposal.Builder()
+                        .addEncryptionAlgorithm(
+                                ENCRYPTION_ALGORITHM_AES_GCM_12, SaProposal.KEY_LEN_AES_128)
+                        .addDhGroup(DH_GROUP_2048_BIT_MODP)
+                        .addPseudorandomFunction(PSEUDORANDOM_FUNCTION_AES128_XCBC)
+                        .build();
+
+        Context mockContext = mock(Context.class);
+        ConnectivityManager mockConnectManager = mock(ConnectivityManager.class);
+        doReturn(mockConnectManager)
+                .when(mockContext)
+                .getSystemService(Context.CONNECTIVITY_SERVICE);
+        doReturn(mock(Network.class)).when(mockConnectManager).getActiveNetwork();
+
+        final String serverHostname = "192.0.2.100";
+        final String testLocalId = "test.client.com";
+        final String testRemoteId = "test.server.com";
+        final byte[] psk = "psk".getBytes();
+
+        IKE_PARAMS =
+                new IkeSessionParams.Builder(mockContext)
+                        .setServerHostname(serverHostname)
+                        .addSaProposal(ikeProposal)
+                        .setLocalIdentification(new IkeFqdnIdentification(testLocalId))
+                        .setRemoteIdentification(new IkeFqdnIdentification(testRemoteId))
+                        .setAuthPsk(psk)
+                        .build();
+
+        ChildSaProposal childProposal =
+                new ChildSaProposal.Builder()
+                        .addEncryptionAlgorithm(
+                                ENCRYPTION_ALGORITHM_AES_GCM_12, SaProposal.KEY_LEN_AES_128)
+                        .build();
+        CHILD_PARAMS =
+                new TunnelModeChildSessionParams.Builder().addSaProposal(childProposal).build();
+    }
+
+    // Package private for use in VcnGatewayConnectionConfigTest
+    static VcnControlPlaneIkeConfig buildTestConfig() {
+        return new VcnControlPlaneIkeConfig(IKE_PARAMS, CHILD_PARAMS);
+    }
+
+    @Test
+    public void testGetters() {
+        final VcnControlPlaneIkeConfig config = buildTestConfig();
+        assertEquals(IKE_PARAMS, config.getIkeSessionParams());
+        assertEquals(CHILD_PARAMS, config.getChildSessionParams());
+    }
+
+    @Test
+    public void testConstructConfigWithoutIkeParams() {
+        try {
+            new VcnControlPlaneIkeConfig(null, CHILD_PARAMS);
+            fail("Expect to fail because ikeParams was null");
+        } catch (NullPointerException expected) {
+        }
+    }
+
+    @Test
+    public void testBuilderConfigWithoutChildParams() {
+        try {
+            new VcnControlPlaneIkeConfig(IKE_PARAMS, null);
+            fail("Expect to fail because childParams was null");
+        } catch (NullPointerException expected) {
+        }
+    }
+}
diff --git a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
index 3e659d0..5b17aad 100644
--- a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.fail;
 
 import android.net.NetworkCapabilities;
@@ -57,17 +58,22 @@
             };
     public static final int MAX_MTU = 1360;
 
+    public static final VcnControlPlaneConfig CONTROL_PLANE_CONFIG =
+            VcnControlPlaneIkeConfigTest.buildTestConfig();
+
     // Public for use in VcnGatewayConnectionTest
     public static VcnGatewayConnectionConfig buildTestConfig() {
         return buildTestConfigWithExposedCaps(EXPOSED_CAPS);
     }
 
+    private static VcnGatewayConnectionConfig.Builder newBuilder() {
+        return new VcnGatewayConnectionConfig.Builder(CONTROL_PLANE_CONFIG);
+    }
+
     // Public for use in VcnGatewayConnectionTest
     public static VcnGatewayConnectionConfig buildTestConfigWithExposedCaps(int... exposedCaps) {
         final VcnGatewayConnectionConfig.Builder builder =
-                new VcnGatewayConnectionConfig.Builder()
-                        .setRetryInterval(RETRY_INTERVALS_MS)
-                        .setMaxMtu(MAX_MTU);
+                newBuilder().setRetryInterval(RETRY_INTERVALS_MS).setMaxMtu(MAX_MTU);
 
         for (int caps : exposedCaps) {
             builder.addExposedCapability(caps);
@@ -81,9 +87,19 @@
     }
 
     @Test
+    public void testBuilderRequiresNonNullControlPlaneConfig() {
+        try {
+            new VcnGatewayConnectionConfig.Builder(null).build();
+
+            fail("Expected exception due to invalid control plane config");
+        } catch (NullPointerException e) {
+        }
+    }
+
+    @Test
     public void testBuilderRequiresNonEmptyExposedCaps() {
         try {
-            new VcnGatewayConnectionConfig.Builder()
+            newBuilder()
                     .addRequiredUnderlyingCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                     .build();
 
@@ -95,9 +111,7 @@
     @Test
     public void testBuilderRequiresNonEmptyUnderlyingCaps() {
         try {
-            new VcnGatewayConnectionConfig.Builder()
-                    .addExposedCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                    .build();
+            newBuilder().addExposedCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET).build();
 
             fail("Expected exception due to invalid required underlying capabilities");
         } catch (IllegalArgumentException e) {
@@ -107,7 +121,7 @@
     @Test
     public void testBuilderRequiresNonNullRetryInterval() {
         try {
-            new VcnGatewayConnectionConfig.Builder().setRetryInterval(null);
+            newBuilder().setRetryInterval(null);
             fail("Expected exception due to invalid retryIntervalMs");
         } catch (IllegalArgumentException e) {
         }
@@ -116,7 +130,7 @@
     @Test
     public void testBuilderRequiresNonEmptyRetryInterval() {
         try {
-            new VcnGatewayConnectionConfig.Builder().setRetryInterval(new long[0]);
+            newBuilder().setRetryInterval(new long[0]);
             fail("Expected exception due to invalid retryIntervalMs");
         } catch (IllegalArgumentException e) {
         }
@@ -125,8 +139,7 @@
     @Test
     public void testBuilderRequiresValidMtu() {
         try {
-            new VcnGatewayConnectionConfig.Builder()
-                    .setMaxMtu(VcnGatewayConnectionConfig.MIN_MTU_V6 - 1);
+            newBuilder().setMaxMtu(VcnGatewayConnectionConfig.MIN_MTU_V6 - 1);
             fail("Expected exception due to invalid mtu");
         } catch (IllegalArgumentException e) {
         }
@@ -144,6 +157,9 @@
         Arrays.sort(underlyingCaps);
         assertArrayEquals(UNDERLYING_CAPS, underlyingCaps);
 
+        assertEquals(CONTROL_PLANE_CONFIG, config.getControlPlaneConfig());
+        assertFalse(CONTROL_PLANE_CONFIG == config.getControlPlaneConfig());
+
         assertArrayEquals(RETRY_INTERVALS_MS, config.getRetryIntervalsMs());
         assertEquals(MAX_MTU, config.getMaxMtu());
     }
diff --git a/tests/vcn/java/android/net/vcn/VcnManagerTest.java b/tests/vcn/java/android/net/vcn/VcnManagerTest.java
index 7dada9d..7087676 100644
--- a/tests/vcn/java/android/net/vcn/VcnManagerTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnManagerTest.java
@@ -22,28 +22,40 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.notNull;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.net.LinkProperties;
 import android.net.NetworkCapabilities;
+import android.net.vcn.VcnManager.VcnStatusCallback;
+import android.net.vcn.VcnManager.VcnStatusCallbackBinder;
 import android.net.vcn.VcnManager.VcnUnderlyingNetworkPolicyListener;
+import android.os.ParcelUuid;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 
+import java.net.UnknownHostException;
+import java.util.UUID;
 import java.util.concurrent.Executor;
 
 public class VcnManagerTest {
+    private static final ParcelUuid SUB_GROUP = new ParcelUuid(new UUID(0, 0));
+    private static final int[] UNDERLYING_NETWORK_CAPABILITIES = {
+        NetworkCapabilities.NET_CAPABILITY_IMS, NetworkCapabilities.NET_CAPABILITY_INTERNET
+    };
     private static final Executor INLINE_EXECUTOR = Runnable::run;
 
     private IVcnManagementService mMockVcnManagementService;
     private VcnUnderlyingNetworkPolicyListener mMockPolicyListener;
+    private VcnStatusCallback mMockStatusCallback;
 
     private Context mContext;
     private VcnManager mVcnManager;
@@ -52,6 +64,7 @@
     public void setUp() {
         mMockVcnManagementService = mock(IVcnManagementService.class);
         mMockPolicyListener = mock(VcnUnderlyingNetworkPolicyListener.class);
+        mMockStatusCallback = mock(VcnStatusCallback.class);
 
         mContext = getContext();
         mVcnManager = new VcnManager(mContext, mMockVcnManagementService);
@@ -132,4 +145,74 @@
     public void testGetUnderlyingNetworkPolicyNullLinkProperties() throws Exception {
         mVcnManager.getUnderlyingNetworkPolicy(new NetworkCapabilities(), null);
     }
+
+    @Test
+    public void testRegisterVcnStatusCallback() throws Exception {
+        mVcnManager.registerVcnStatusCallback(SUB_GROUP, INLINE_EXECUTOR, mMockStatusCallback);
+
+        verify(mMockVcnManagementService)
+                .registerVcnStatusCallback(eq(SUB_GROUP), notNull(), any());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testRegisterVcnStatusCallbackAlreadyRegistered() throws Exception {
+        mVcnManager.registerVcnStatusCallback(SUB_GROUP, INLINE_EXECUTOR, mMockStatusCallback);
+        mVcnManager.registerVcnStatusCallback(SUB_GROUP, INLINE_EXECUTOR, mMockStatusCallback);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testRegisterVcnStatusCallbackNullSubscriptionGroup() throws Exception {
+        mVcnManager.registerVcnStatusCallback(null, INLINE_EXECUTOR, mMockStatusCallback);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testRegisterVcnStatusCallbackNullExecutor() throws Exception {
+        mVcnManager.registerVcnStatusCallback(SUB_GROUP, null, mMockStatusCallback);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testRegisterVcnStatusCallbackNullCallback() throws Exception {
+        mVcnManager.registerVcnStatusCallback(SUB_GROUP, INLINE_EXECUTOR, null);
+    }
+
+    @Test
+    public void testUnregisterVcnStatusCallback() throws Exception {
+        mVcnManager.registerVcnStatusCallback(SUB_GROUP, INLINE_EXECUTOR, mMockStatusCallback);
+
+        mVcnManager.unregisterVcnStatusCallback(mMockStatusCallback);
+
+        verify(mMockVcnManagementService).unregisterVcnStatusCallback(any());
+    }
+
+    @Test
+    public void testUnregisterUnknownVcnStatusCallback() throws Exception {
+        mVcnManager.unregisterVcnStatusCallback(mMockStatusCallback);
+
+        verifyNoMoreInteractions(mMockVcnManagementService);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testUnregisterNullVcnStatusCallback() throws Exception {
+        mVcnManager.unregisterVcnStatusCallback(null);
+    }
+
+    @Test
+    public void testVcnStatusCallbackBinder() throws Exception {
+        IVcnStatusCallback cbBinder =
+                new VcnStatusCallbackBinder(INLINE_EXECUTOR, mMockStatusCallback);
+
+        cbBinder.onEnteredSafeMode();
+        verify(mMockStatusCallback).onEnteredSafeMode();
+
+        cbBinder.onGatewayConnectionError(
+                UNDERLYING_NETWORK_CAPABILITIES,
+                VcnManager.VCN_ERROR_CODE_NETWORK_ERROR,
+                "java.net.UnknownHostException",
+                "exception_message");
+        verify(mMockStatusCallback)
+                .onGatewayConnectionError(
+                        eq(UNDERLYING_NETWORK_CAPABILITIES),
+                        eq(VcnManager.VCN_ERROR_CODE_NETWORK_ERROR),
+                        any(UnknownHostException.class));
+    }
 }
diff --git a/tests/vcn/java/android/net/vcn/VcnUnderlyingNetworkSpecifierTest.java b/tests/vcn/java/android/net/vcn/VcnUnderlyingNetworkSpecifierTest.java
new file mode 100644
index 0000000..2110d6e
--- /dev/null
+++ b/tests/vcn/java/android/net/vcn/VcnUnderlyingNetworkSpecifierTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vcn;
+
+import static com.android.testutils.ParcelUtils.assertParcelSane;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.net.TelephonyNetworkSpecifier;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class VcnUnderlyingNetworkSpecifierTest {
+    private static final int[] TEST_SUB_IDS = new int[] {1, 2, 3, 5};
+
+    @Test
+    public void testGetSubIds() {
+        final VcnUnderlyingNetworkSpecifier specifier =
+                new VcnUnderlyingNetworkSpecifier(TEST_SUB_IDS);
+
+        assertEquals(TEST_SUB_IDS, specifier.getSubIds());
+    }
+
+    @Test
+    public void testParceling() {
+        final VcnUnderlyingNetworkSpecifier specifier =
+                new VcnUnderlyingNetworkSpecifier(TEST_SUB_IDS);
+        assertParcelSane(specifier, 1);
+    }
+
+    @Test
+    public void testCanBeSatisfiedByTelephonyNetworkSpecifier() {
+        final TelephonyNetworkSpecifier telSpecifier =
+                new TelephonyNetworkSpecifier(TEST_SUB_IDS[0]);
+
+        final VcnUnderlyingNetworkSpecifier specifier =
+                new VcnUnderlyingNetworkSpecifier(TEST_SUB_IDS);
+        assertTrue(specifier.canBeSatisfiedBy(telSpecifier));
+    }
+}
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index c290bff..45b2381 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -26,6 +26,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -42,9 +43,11 @@
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.annotation.NonNull;
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.net.ConnectivityManager;
@@ -52,6 +55,7 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkCapabilities.Transport;
 import android.net.TelephonyNetworkSpecifier;
+import android.net.vcn.IVcnStatusCallback;
 import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
 import android.net.vcn.VcnConfig;
 import android.net.vcn.VcnConfigTest;
@@ -70,7 +74,9 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.server.VcnManagementService.VcnSafemodeCallback;
+import com.android.internal.util.LocationPermissionChecker;
+import com.android.server.VcnManagementService.VcnCallback;
+import com.android.server.VcnManagementService.VcnStatusCallbackInfo;
 import com.android.server.vcn.TelephonySubscriptionTracker;
 import com.android.server.vcn.Vcn;
 import com.android.server.vcn.VcnContext;
@@ -147,14 +153,17 @@
             mock(PersistableBundleUtils.LockingReadWriteHelper.class);
     private final TelephonySubscriptionTracker mSubscriptionTracker =
             mock(TelephonySubscriptionTracker.class);
+    private final LocationPermissionChecker mLocationPermissionChecker =
+            mock(LocationPermissionChecker.class);
 
-    private final ArgumentCaptor<VcnSafemodeCallback> mSafemodeCallbackCaptor =
-            ArgumentCaptor.forClass(VcnSafemodeCallback.class);
+    private final ArgumentCaptor<VcnCallback> mVcnCallbackCaptor =
+            ArgumentCaptor.forClass(VcnCallback.class);
 
     private final VcnManagementService mVcnMgmtSvc;
 
     private final IVcnUnderlyingNetworkPolicyListener mMockPolicyListener =
             mock(IVcnUnderlyingNetworkPolicyListener.class);
+    private final IVcnStatusCallback mMockStatusCallback = mock(IVcnStatusCallback.class);
     private final IBinder mMockIBinder = mock(IBinder.class);
 
     public VcnManagementServiceTest() throws Exception {
@@ -171,6 +180,7 @@
 
         doReturn(TEST_PACKAGE_NAME).when(mMockContext).getOpPackageName();
 
+        doReturn(mMockContext).when(mVcnContext).getContext();
         doReturn(mTestLooper.getLooper()).when(mMockDeps).getLooper();
         doReturn(TEST_UID).when(mMockDeps).getBinderCallingUid();
         doReturn(mVcnContext)
@@ -188,6 +198,9 @@
         doReturn(mConfigReadWriteHelper)
                 .when(mMockDeps)
                 .newPersistableBundleLockingReadWriteHelper(any());
+        doReturn(mLocationPermissionChecker)
+                .when(mMockDeps)
+                .newLocationPermissionChecker(eq(mMockContext));
 
         // Setup VCN instance generation
         doAnswer((invocation) -> {
@@ -206,6 +219,7 @@
         mVcnMgmtSvc = new VcnManagementService(mMockContext, mMockDeps);
 
         doReturn(mMockIBinder).when(mMockPolicyListener).asBinder();
+        doReturn(mMockIBinder).when(mMockStatusCallback).asBinder();
 
         // Make sure the profiles are loaded.
         mTestLooper.dispatchAll();
@@ -707,24 +721,138 @@
         verify(mMockPolicyListener).onPolicyChanged();
     }
 
-    @Test
-    public void testVcnSafemodeCallbackOnEnteredSafemode() throws Exception {
-        TelephonySubscriptionSnapshot snapshot =
-                triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_1));
+    private void verifyVcnCallback(
+            @NonNull ParcelUuid subGroup, @NonNull TelephonySubscriptionSnapshot snapshot)
+            throws Exception {
         verify(mMockDeps)
                 .newVcn(
                         eq(mVcnContext),
-                        eq(TEST_UUID_1),
+                        eq(subGroup),
                         eq(TEST_VCN_CONFIG),
                         eq(snapshot),
-                        mSafemodeCallbackCaptor.capture());
+                        mVcnCallbackCaptor.capture());
 
         mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
 
-        VcnSafemodeCallback safemodeCallback = mSafemodeCallbackCaptor.getValue();
-        safemodeCallback.onEnteredSafemode();
+        VcnCallback vcnCallback = mVcnCallbackCaptor.getValue();
+        vcnCallback.onEnteredSafeMode();
 
-        assertFalse(mVcnMgmtSvc.getAllVcns().get(TEST_UUID_1).isActive());
         verify(mMockPolicyListener).onPolicyChanged();
     }
+
+    @Test
+    public void testVcnCallbackOnEnteredSafeMode() throws Exception {
+        TelephonySubscriptionSnapshot snapshot =
+                triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_1));
+
+        verifyVcnCallback(TEST_UUID_1, snapshot);
+    }
+
+    private void triggerVcnStatusCallbackOnEnteredSafeMode(
+            @NonNull ParcelUuid subGroup,
+            @NonNull String pkgName,
+            int uid,
+            boolean hasPermissionsforSubGroup,
+            boolean hasLocationPermission)
+            throws Exception {
+        TelephonySubscriptionSnapshot snapshot =
+                triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(subGroup));
+
+        doReturn(hasPermissionsforSubGroup)
+                .when(snapshot)
+                .packageHasPermissionsForSubscriptionGroup(eq(subGroup), eq(pkgName));
+
+        doReturn(hasLocationPermission)
+                .when(mLocationPermissionChecker)
+                .checkLocationPermission(eq(pkgName), any(), eq(uid), any());
+
+        mVcnMgmtSvc.registerVcnStatusCallback(subGroup, mMockStatusCallback, pkgName);
+
+        // Trigger systemReady() to set up LocationPermissionChecker
+        mVcnMgmtSvc.systemReady();
+
+        verifyVcnCallback(subGroup, snapshot);
+    }
+
+    @Test
+    public void testVcnStatusCallbackOnEnteredSafeModeWithCarrierPrivileges() throws Exception {
+        triggerVcnStatusCallbackOnEnteredSafeMode(
+                TEST_UUID_1,
+                TEST_PACKAGE_NAME,
+                TEST_UID,
+                true /* hasPermissionsforSubGroup */,
+                true /* hasLocationPermission */);
+
+        verify(mMockStatusCallback, times(1)).onEnteredSafeMode();
+    }
+
+    @Test
+    public void testVcnStatusCallbackOnEnteredSafeModeWithoutCarrierPrivileges() throws Exception {
+        triggerVcnStatusCallbackOnEnteredSafeMode(
+                TEST_UUID_1,
+                TEST_PACKAGE_NAME,
+                TEST_UID,
+                false /* hasPermissionsforSubGroup */,
+                true /* hasLocationPermission */);
+
+        verify(mMockStatusCallback, never()).onEnteredSafeMode();
+    }
+
+    @Test
+    public void testVcnStatusCallbackOnEnteredSafeModeWithoutLocationPermission() throws Exception {
+        triggerVcnStatusCallbackOnEnteredSafeMode(
+                TEST_UUID_1,
+                TEST_PACKAGE_NAME,
+                TEST_UID,
+                true /* hasPermissionsforSubGroup */,
+                false /* hasLocationPermission */);
+
+        verify(mMockStatusCallback, never()).onEnteredSafeMode();
+    }
+
+    @Test
+    public void testRegisterVcnStatusCallback() throws Exception {
+        mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME);
+
+        Map<IBinder, VcnStatusCallbackInfo> callbacks = mVcnMgmtSvc.getAllStatusCallbacks();
+        VcnStatusCallbackInfo cbInfo = callbacks.get(mMockIBinder);
+
+        assertNotNull(cbInfo);
+        assertEquals(TEST_UUID_1, cbInfo.mSubGroup);
+        assertEquals(mMockStatusCallback, cbInfo.mCallback);
+        assertEquals(TEST_PACKAGE_NAME, cbInfo.mPkgName);
+        assertEquals(TEST_UID, cbInfo.mUid);
+        verify(mMockIBinder).linkToDeath(eq(cbInfo), anyInt());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testRegisterVcnStatusCallbackDuplicate() {
+        mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME);
+        mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME);
+    }
+
+    @Test
+    public void testUnregisterVcnStatusCallback() {
+        mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME);
+        Map<IBinder, VcnStatusCallbackInfo> callbacks = mVcnMgmtSvc.getAllStatusCallbacks();
+        VcnStatusCallbackInfo cbInfo = callbacks.get(mMockIBinder);
+
+        mVcnMgmtSvc.unregisterVcnStatusCallback(mMockStatusCallback);
+        assertTrue(mVcnMgmtSvc.getAllStatusCallbacks().isEmpty());
+        verify(mMockIBinder).unlinkToDeath(eq(cbInfo), anyInt());
+    }
+
+    @Test(expected = SecurityException.class)
+    public void testRegisterVcnStatusCallbackInvalidPackage() {
+        doThrow(new SecurityException()).when(mAppOpsMgr).checkPackage(TEST_UID, TEST_PACKAGE_NAME);
+
+        mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME);
+    }
+
+    @Test
+    public void testUnregisterVcnStatusCallbackNeverRegistered() {
+        mVcnMgmtSvc.unregisterVcnStatusCallback(mMockStatusCallback);
+
+        assertTrue(mVcnMgmtSvc.getAllStatusCallbacks().isEmpty());
+    }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
index e715480..69c21b9 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
@@ -20,6 +20,9 @@
 import static android.net.IpSecManager.DIRECTION_OUT;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.vcn.VcnManager.VCN_ERROR_CODE_CONFIG_ERROR;
+import static android.net.vcn.VcnManager.VCN_ERROR_CODE_INTERNAL_ERROR;
+import static android.net.vcn.VcnManager.VCN_ERROR_CODE_NETWORK_ERROR;
 
 import static com.android.server.vcn.VcnGatewayConnection.VcnChildSessionConfiguration;
 import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession;
@@ -39,6 +42,11 @@
 import android.net.LinkProperties;
 import android.net.NetworkAgent;
 import android.net.NetworkCapabilities;
+import android.net.ipsec.ike.exceptions.AuthenticationFailedException;
+import android.net.ipsec.ike.exceptions.IkeException;
+import android.net.ipsec.ike.exceptions.IkeInternalException;
+import android.net.ipsec.ike.exceptions.TemporaryFailureException;
+import android.net.vcn.VcnManager.VcnErrorCode;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -48,6 +56,8 @@
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 
+import java.io.IOException;
+import java.net.UnknownHostException;
 import java.util.Collections;
 
 /** Tests for VcnGatewayConnection.ConnectedState */
@@ -75,8 +85,8 @@
     }
 
     @Test
-    public void testEnterStateDoesNotCancelSafemodeAlarm() {
-        verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+    public void testEnterStateDoesNotCancelSafeModeAlarm() {
+        verifySafeModeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
     }
 
     @Test
@@ -144,7 +154,7 @@
     @Test
     public void testChildOpenedRegistersNetwork() throws Exception {
         // Verify scheduled but not canceled when entering ConnectedState
-        verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+        verifySafeModeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
 
         final VcnChildSessionConfiguration mMockChildSessionConfig =
                 mock(VcnChildSessionConfiguration.class);
@@ -188,17 +198,17 @@
             assertTrue(nc.hasCapability(cap));
         }
 
-        // Now that Vcn Network is up, notify it as validated and verify the Safemode alarm is
+        // Now that Vcn Network is up, notify it as validated and verify the SafeMode alarm is
         // canceled
         mGatewayConnection.mNetworkAgent.onValidationStatus(
                 NetworkAgent.VALIDATION_STATUS_VALID, null /* redirectUri */);
-        verify(mSafemodeTimeoutAlarm).cancel();
+        verify(mSafeModeTimeoutAlarm).cancel();
     }
 
     @Test
     public void testChildSessionClosedTriggersDisconnect() throws Exception {
         // Verify scheduled but not canceled when entering ConnectedState
-        verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+        verifySafeModeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
 
         getChildSessionCallback().onClosed();
         mTestLooper.dispatchAll();
@@ -206,14 +216,33 @@
         assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
         verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */);
 
-        // Since network never validated, verify mSafemodeTimeoutAlarm not canceled
-        verifyNoMoreInteractions(mSafemodeTimeoutAlarm);
+        // Since network never validated, verify mSafeModeTimeoutAlarm not canceled
+        verifyNoMoreInteractions(mSafeModeTimeoutAlarm);
+
+        // The child session was closed without exception, so verify that the GatewayStatusCallback
+        // was not notified
+        verifyNoMoreInteractions(mGatewayStatusCallback);
+    }
+
+    @Test
+    public void testChildSessionClosedExceptionallyNotifiesGatewayStatusCallback()
+            throws Exception {
+        final IkeInternalException exception = new IkeInternalException(mock(IOException.class));
+        getChildSessionCallback().onClosedExceptionally(exception);
+        mTestLooper.dispatchAll();
+
+        verify(mGatewayStatusCallback)
+                .onGatewayConnectionError(
+                        eq(mConfig.getRequiredUnderlyingCapabilities()),
+                        eq(VCN_ERROR_CODE_INTERNAL_ERROR),
+                        any(),
+                        any());
     }
 
     @Test
     public void testIkeSessionClosedTriggersDisconnect() throws Exception {
         // Verify scheduled but not canceled when entering ConnectedState
-        verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+        verifySafeModeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
 
         getIkeSessionCallback().onClosed();
         mTestLooper.dispatchAll();
@@ -221,7 +250,44 @@
         assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState());
         verify(mIkeSession).close();
 
-        // Since network never validated, verify mSafemodeTimeoutAlarm not canceled
-        verifyNoMoreInteractions(mSafemodeTimeoutAlarm);
+        // Since network never validated, verify mSafeModeTimeoutAlarm not canceled
+        verifyNoMoreInteractions(mSafeModeTimeoutAlarm);
+
+        // IkeSession closed with no error, so verify that the GatewayStatusCallback was not
+        // notified
+        verifyNoMoreInteractions(mGatewayStatusCallback);
+    }
+
+    private void verifyIkeSessionClosedExceptionalltyNotifiesStatusCallback(
+            IkeException cause, @VcnErrorCode int expectedErrorType) {
+        getIkeSessionCallback().onClosedExceptionally(cause);
+        mTestLooper.dispatchAll();
+
+        verify(mIkeSession).close();
+
+        verify(mGatewayStatusCallback)
+                .onGatewayConnectionError(
+                        eq(mConfig.getRequiredUnderlyingCapabilities()),
+                        eq(expectedErrorType),
+                        any(),
+                        any());
+    }
+
+    @Test
+    public void testIkeSessionClosedExceptionallyAuthenticationFailure() throws Exception {
+        verifyIkeSessionClosedExceptionalltyNotifiesStatusCallback(
+                new AuthenticationFailedException("vcn test"), VCN_ERROR_CODE_CONFIG_ERROR);
+    }
+
+    @Test
+    public void testIkeSessionClosedExceptionallyDnsFailure() throws Exception {
+        verifyIkeSessionClosedExceptionalltyNotifiesStatusCallback(
+                new IkeInternalException(new UnknownHostException()), VCN_ERROR_CODE_NETWORK_ERROR);
+    }
+
+    @Test
+    public void testIkeSessionClosedExceptionallyInternalFailure() throws Exception {
+        verifyIkeSessionClosedExceptionalltyNotifiesStatusCallback(
+                new TemporaryFailureException("vcn test"), VCN_ERROR_CODE_INTERNAL_ERROR);
     }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java
index 07282c9..17ae19e 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java
@@ -108,7 +108,7 @@
     }
 
     @Test
-    public void testSafemodeTimeoutNotifiesCallback() {
-        verifySafemodeTimeoutNotifiesCallback(mGatewayConnection.mConnectingState);
+    public void testSafeModeTimeoutNotifiesCallback() {
+        verifySafeModeTimeoutNotifiesCallback(mGatewayConnection.mConnectingState);
     }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
index 49ce54d..9ea641f 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
@@ -79,7 +79,7 @@
         mTestLooper.dispatchAll();
 
         assertEquals(mGatewayConnection.mConnectingState, mGatewayConnection.getCurrentState());
-        verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+        verifySafeModeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
     }
 
     @Test
@@ -100,6 +100,6 @@
 
         assertNull(mGatewayConnection.getCurrentState());
         verify(mIpSecSvc).deleteTunnelInterface(eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), any());
-        verifySafemodeTimeoutAlarmAndGetCallback(true /* expectCanceled */);
+        verifySafeModeTimeoutAlarmAndGetCallback(true /* expectCanceled */);
     }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java
index 22eab2a..7385204 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java
@@ -82,7 +82,7 @@
     }
 
     @Test
-    public void testSafemodeTimeoutNotifiesCallback() {
-        verifySafemodeTimeoutNotifiesCallback(mGatewayConnection.mDisconnectingState);
+    public void testSafeModeTimeoutNotifiesCallback() {
+        verifySafeModeTimeoutNotifiesCallback(mGatewayConnection.mDisconnectingState);
     }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java
index 6c26075..5b0850b 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java
@@ -93,7 +93,7 @@
     }
 
     @Test
-    public void testSafemodeTimeoutNotifiesCallback() {
-        verifySafemodeTimeoutNotifiesCallback(mGatewayConnection.mRetryTimeoutState);
+    public void testSafeModeTimeoutNotifiesCallback() {
+        verifySafeModeTimeoutNotifiesCallback(mGatewayConnection.mRetryTimeoutState);
     }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
index ac9ec06..a660735 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
@@ -110,7 +110,7 @@
     @NonNull protected final WakeupMessage mTeardownTimeoutAlarm;
     @NonNull protected final WakeupMessage mDisconnectRequestAlarm;
     @NonNull protected final WakeupMessage mRetryTimeoutAlarm;
-    @NonNull protected final WakeupMessage mSafemodeTimeoutAlarm;
+    @NonNull protected final WakeupMessage mSafeModeTimeoutAlarm;
 
     @NonNull protected final IpSecService mIpSecSvc;
     @NonNull protected final ConnectivityManager mConnMgr;
@@ -131,7 +131,7 @@
         mTeardownTimeoutAlarm = mock(WakeupMessage.class);
         mDisconnectRequestAlarm = mock(WakeupMessage.class);
         mRetryTimeoutAlarm = mock(WakeupMessage.class);
-        mSafemodeTimeoutAlarm = mock(WakeupMessage.class);
+        mSafeModeTimeoutAlarm = mock(WakeupMessage.class);
 
         mIpSecSvc = mock(IpSecService.class);
         setupIpSecManager(mContext, mIpSecSvc);
@@ -154,7 +154,7 @@
         setUpWakeupMessage(mTeardownTimeoutAlarm, VcnGatewayConnection.TEARDOWN_TIMEOUT_ALARM);
         setUpWakeupMessage(mDisconnectRequestAlarm, VcnGatewayConnection.DISCONNECT_REQUEST_ALARM);
         setUpWakeupMessage(mRetryTimeoutAlarm, VcnGatewayConnection.RETRY_TIMEOUT_ALARM);
-        setUpWakeupMessage(mSafemodeTimeoutAlarm, VcnGatewayConnection.SAFEMODE_TIMEOUT_ALARM);
+        setUpWakeupMessage(mSafeModeTimeoutAlarm, VcnGatewayConnection.SAFEMODE_TIMEOUT_ALARM);
 
         doReturn(ELAPSED_REAL_TIME).when(mDeps).getElapsedRealTime();
     }
@@ -259,23 +259,23 @@
                 expectCanceled);
     }
 
-    protected Runnable verifySafemodeTimeoutAlarmAndGetCallback(boolean expectCanceled) {
+    protected Runnable verifySafeModeTimeoutAlarmAndGetCallback(boolean expectCanceled) {
         return verifyWakeupMessageSetUpAndGetCallback(
                 VcnGatewayConnection.SAFEMODE_TIMEOUT_ALARM,
-                mSafemodeTimeoutAlarm,
+                mSafeModeTimeoutAlarm,
                 TimeUnit.SECONDS.toMillis(VcnGatewayConnection.SAFEMODE_TIMEOUT_SECONDS),
                 expectCanceled);
     }
 
-    protected void verifySafemodeTimeoutNotifiesCallback(@NonNull State expectedState) {
-        // Safemode timer starts when VcnGatewayConnection exits DisconnectedState (the initial
+    protected void verifySafeModeTimeoutNotifiesCallback(@NonNull State expectedState) {
+        // SafeMode timer starts when VcnGatewayConnection exits DisconnectedState (the initial
         // state)
         final Runnable delayedEvent =
-                verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+                verifySafeModeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
         delayedEvent.run();
         mTestLooper.dispatchAll();
 
-        verify(mGatewayStatusCallback).onEnteredSafemode();
+        verify(mGatewayStatusCallback).onEnteredSafeMode();
         assertEquals(expectedState, mGatewayConnection.getCurrentState());
     }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnTest.java b/tests/vcn/java/com/android/server/vcn/VcnTest.java
index 66cbf84..9d33682 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnTest.java
@@ -34,7 +34,7 @@
 import android.os.ParcelUuid;
 import android.os.test.TestLooper;
 
-import com.android.server.VcnManagementService.VcnSafemodeCallback;
+import com.android.server.VcnManagementService.VcnCallback;
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
 import com.android.server.vcn.Vcn.VcnGatewayStatusCallback;
 import com.android.server.vcn.VcnNetworkProvider.NetworkRequestListener;
@@ -56,7 +56,7 @@
     private VcnContext mVcnContext;
     private TelephonySubscriptionSnapshot mSubscriptionSnapshot;
     private VcnNetworkProvider mVcnNetworkProvider;
-    private VcnSafemodeCallback mVcnSafemodeCallback;
+    private VcnCallback mVcnCallback;
     private Vcn.Dependencies mDeps;
 
     private ArgumentCaptor<VcnGatewayStatusCallback> mGatewayStatusCallbackCaptor;
@@ -72,7 +72,7 @@
         mVcnContext = mock(VcnContext.class);
         mSubscriptionSnapshot = mock(TelephonySubscriptionSnapshot.class);
         mVcnNetworkProvider = mock(VcnNetworkProvider.class);
-        mVcnSafemodeCallback = mock(VcnSafemodeCallback.class);
+        mVcnCallback = mock(VcnCallback.class);
         mDeps = mock(Vcn.Dependencies.class);
 
         mTestLooper = new TestLooper();
@@ -104,7 +104,7 @@
                         TEST_SUB_GROUP,
                         mConfig,
                         mSubscriptionSnapshot,
-                        mVcnSafemodeCallback,
+                        mVcnCallback,
                         mDeps);
     }
 
@@ -148,7 +148,7 @@
     }
 
     @Test
-    public void testGatewayEnteringSafemodeNotifiesVcn() {
+    public void testGatewayEnteringSafeModeNotifiesVcn() {
         final NetworkRequestListener requestListener = verifyAndGetRequestListener();
         for (final int capability : VcnGatewayConnectionConfigTest.EXPOSED_CAPS) {
             startVcnGatewayWithCapabilities(requestListener, capability);
@@ -168,16 +168,17 @@
                         any(),
                         mGatewayStatusCallbackCaptor.capture());
 
-        // Doesn't matter which callback this gets - any Gateway entering Safemode should shut down
+        // Doesn't matter which callback this gets - any Gateway entering safe mode should shut down
         // all Gateways
         final VcnGatewayStatusCallback statusCallback = mGatewayStatusCallbackCaptor.getValue();
-        statusCallback.onEnteredSafemode();
+        statusCallback.onEnteredSafeMode();
         mTestLooper.dispatchAll();
 
+        assertFalse(mVcn.isActive());
         for (final VcnGatewayConnection gatewayConnection : gatewayConnections) {
             verify(gatewayConnection).teardownAsynchronously();
         }
         verify(mVcnNetworkProvider).unregisterListener(requestListener);
-        verify(mVcnSafemodeCallback).onEnteredSafemode();
+        verify(mVcnCallback).onEnteredSafeMode();
     }
 }