Merge "Add system prompt for system language change after <Set Menu Language> CEC message"
diff --git a/core/api/current.txt b/core/api/current.txt
index 11e0a6b..e2325b0 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -135,9 +135,6 @@
     field public static final String READ_HOME_APP_SEARCH_DATA = "android.permission.READ_HOME_APP_SEARCH_DATA";
     field @Deprecated public static final String READ_INPUT_STATE = "android.permission.READ_INPUT_STATE";
     field public static final String READ_LOGS = "android.permission.READ_LOGS";
-    field public static final String READ_MEDIA_AUDIO = "android.permission.READ_MEDIA_AUDIO";
-    field public static final String READ_MEDIA_IMAGE = "android.permission.READ_MEDIA_IMAGE";
-    field public static final String READ_MEDIA_VIDEO = "android.permission.READ_MEDIA_VIDEO";
     field public static final String READ_NEARBY_STREAMING_POLICY = "android.permission.READ_NEARBY_STREAMING_POLICY";
     field public static final String READ_PHONE_NUMBERS = "android.permission.READ_PHONE_NUMBERS";
     field public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE";
@@ -223,8 +220,6 @@
     field public static final String NEARBY_DEVICES = "android.permission-group.NEARBY_DEVICES";
     field public static final String NOTIFICATIONS = "android.permission-group.NOTIFICATIONS";
     field public static final String PHONE = "android.permission-group.PHONE";
-    field public static final String READ_MEDIA_AURAL = "android.permission-group.READ_MEDIA_AURAL";
-    field public static final String READ_MEDIA_VISUAL = "android.permission-group.READ_MEDIA_VISUAL";
     field public static final String SENSORS = "android.permission-group.SENSORS";
     field public static final String SMS = "android.permission-group.SMS";
     field public static final String STORAGE = "android.permission-group.STORAGE";
@@ -7311,10 +7306,10 @@
     method @Nullable public java.util.List<java.lang.String> getDelegatePackages(@NonNull android.content.ComponentName, @NonNull String);
     method @NonNull public java.util.List<java.lang.String> getDelegatedScopes(@Nullable android.content.ComponentName, @NonNull String);
     method public CharSequence getDeviceOwnerLockScreenInfo();
-    method @NonNull public android.graphics.drawable.Drawable getDrawable(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
-    method @NonNull public android.graphics.drawable.Drawable getDrawable(@NonNull String, @NonNull String, @NonNull String, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
-    method @NonNull public android.graphics.drawable.Drawable getDrawableForDensity(@NonNull String, @NonNull String, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
-    method @NonNull public android.graphics.drawable.Drawable getDrawableForDensity(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
+    method @Nullable public android.graphics.drawable.Drawable getDrawable(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
+    method @Nullable public android.graphics.drawable.Drawable getDrawable(@NonNull String, @NonNull String, @NonNull String, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
+    method @Nullable public android.graphics.drawable.Drawable getDrawableForDensity(@NonNull String, @NonNull String, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
+    method @Nullable public android.graphics.drawable.Drawable getDrawableForDensity(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
     method public CharSequence getEndUserSessionMessage(@NonNull android.content.ComponentName);
     method @NonNull public String getEnrollmentSpecificId();
     method @Nullable public android.app.admin.FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(@Nullable android.content.ComponentName);
@@ -22728,6 +22723,7 @@
     method public int describeContents();
     method @Nullable public String getClientPackageName();
     method public int getConnectionState();
+    method @NonNull public java.util.Set<java.lang.String> getDeduplicationIds();
     method @Nullable public CharSequence getDescription();
     method @Nullable public android.os.Bundle getExtras();
     method @NonNull public java.util.List<java.lang.String> getFeatures();
@@ -22761,6 +22757,7 @@
     method @NonNull public android.media.MediaRoute2Info.Builder clearFeatures();
     method @NonNull public android.media.MediaRoute2Info.Builder setClientPackageName(@Nullable String);
     method @NonNull public android.media.MediaRoute2Info.Builder setConnectionState(int);
+    method @NonNull public android.media.MediaRoute2Info.Builder setDeduplicationIds(@NonNull java.util.Set<java.lang.String>);
     method @NonNull public android.media.MediaRoute2Info.Builder setDescription(@Nullable CharSequence);
     method @NonNull public android.media.MediaRoute2Info.Builder setExtras(@Nullable android.os.Bundle);
     method @NonNull public android.media.MediaRoute2Info.Builder setIconUri(@Nullable android.net.Uri);
@@ -23287,8 +23284,12 @@
 
   public final class RouteDiscoveryPreference implements android.os.Parcelable {
     method public int describeContents();
+    method @NonNull public java.util.List<java.lang.String> getAllowedPackages();
+    method @NonNull public java.util.List<java.lang.String> getDeduplicationPackageOrder();
     method @NonNull public java.util.List<java.lang.String> getPreferredFeatures();
+    method @NonNull public java.util.List<java.lang.String> getRequiredFeatures();
     method public boolean shouldPerformActiveScan();
+    method public boolean shouldRemoveDuplicates();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.media.RouteDiscoveryPreference> CREATOR;
   }
@@ -23297,7 +23298,10 @@
     ctor public RouteDiscoveryPreference.Builder(@NonNull java.util.List<java.lang.String>, boolean);
     ctor public RouteDiscoveryPreference.Builder(@NonNull android.media.RouteDiscoveryPreference);
     method @NonNull public android.media.RouteDiscoveryPreference build();
+    method @NonNull public android.media.RouteDiscoveryPreference.Builder setAllowedPackages(@NonNull java.util.List<java.lang.String>);
+    method @NonNull public android.media.RouteDiscoveryPreference.Builder setDeduplicationPackageOrder(@NonNull java.util.List<java.lang.String>);
     method @NonNull public android.media.RouteDiscoveryPreference.Builder setPreferredFeatures(@NonNull java.util.List<java.lang.String>);
+    method @NonNull public android.media.RouteDiscoveryPreference.Builder setRequiredFeatures(@NonNull java.util.List<java.lang.String>);
     method @NonNull public android.media.RouteDiscoveryPreference.Builder setShouldPerformActiveScan(boolean);
   }
 
@@ -31734,9 +31738,14 @@
     method public void release();
     method public void release(int);
     method public void setReferenceCounted(boolean);
+    method public void setStateListener(@NonNull java.util.concurrent.Executor, @Nullable android.os.PowerManager.WakeLockStateListener);
     method public void setWorkSource(android.os.WorkSource);
   }
 
+  public static interface PowerManager.WakeLockStateListener {
+    method public void onStateChanged(boolean);
+  }
+
   public class Process {
     ctor public Process();
     method public static final long getElapsedCpuTime();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index d6e4815..b3dd7a7 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -166,6 +166,7 @@
     field public static final String MANAGE_DEBUGGING = "android.permission.MANAGE_DEBUGGING";
     field public static final String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS";
     field public static final String MANAGE_FACTORY_RESET_PROTECTION = "android.permission.MANAGE_FACTORY_RESET_PROTECTION";
+    field public static final String MANAGE_GAME_ACTIVITY = "android.permission.MANAGE_GAME_ACTIVITY";
     field public static final String MANAGE_GAME_MODE = "android.permission.MANAGE_GAME_MODE";
     field public static final String MANAGE_HOTWORD_DETECTION = "android.permission.MANAGE_HOTWORD_DETECTION";
     field public static final String MANAGE_IPSEC_TUNNELS = "android.permission.MANAGE_IPSEC_TUNNELS";
@@ -202,6 +203,7 @@
     field public static final String MODIFY_PARENTAL_CONTROLS = "android.permission.MODIFY_PARENTAL_CONTROLS";
     field public static final String MODIFY_QUIET_MODE = "android.permission.MODIFY_QUIET_MODE";
     field public static final String MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE = "android.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE";
+    field public static final String MODIFY_TOUCH_MODE_STATE = "android.permission.MODIFY_TOUCH_MODE_STATE";
     field public static final String MOVE_PACKAGE = "android.permission.MOVE_PACKAGE";
     field public static final String NETWORK_AIRPLANE_MODE = "android.permission.NETWORK_AIRPLANE_MODE";
     field public static final String NETWORK_CARRIER_PROVISIONING = "android.permission.NETWORK_CARRIER_PROVISIONING";
@@ -259,6 +261,7 @@
     field public static final String READ_WALLPAPER_INTERNAL = "android.permission.READ_WALLPAPER_INTERNAL";
     field public static final String READ_WIFI_CREDENTIAL = "android.permission.READ_WIFI_CREDENTIAL";
     field public static final String REAL_GET_TASKS = "android.permission.REAL_GET_TASKS";
+    field public static final String RECEIVE_BLUETOOTH_MAP = "android.permission.RECEIVE_BLUETOOTH_MAP";
     field public static final String RECEIVE_DATA_ACTIVITY_CHANGE = "android.permission.RECEIVE_DATA_ACTIVITY_CHANGE";
     field public static final String RECEIVE_DEVICE_CUSTOMIZATION_READY = "android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY";
     field public static final String RECEIVE_EMERGENCY_BROADCAST = "android.permission.RECEIVE_EMERGENCY_BROADCAST";
@@ -303,6 +306,7 @@
     field public static final String SET_POINTER_SPEED = "android.permission.SET_POINTER_SPEED";
     field public static final String SET_SCREEN_COMPATIBILITY = "android.permission.SET_SCREEN_COMPATIBILITY";
     field public static final String SET_SYSTEM_AUDIO_CAPTION = "android.permission.SET_SYSTEM_AUDIO_CAPTION";
+    field public static final String SET_UNRESTRICTED_KEEP_CLEAR_AREAS = "android.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS";
     field public static final String SET_VOLUME_KEY_LONG_PRESS_LISTENER = "android.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER";
     field public static final String SET_WALLPAPER_COMPONENT = "android.permission.SET_WALLPAPER_COMPONENT";
     field public static final String SET_WALLPAPER_DIM_AMOUNT = "android.permission.SET_WALLPAPER_DIM_AMOUNT";
@@ -349,6 +353,7 @@
     field public static final String WRITE_EMBEDDED_SUBSCRIPTIONS = "android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS";
     field @Deprecated public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE";
     field public static final String WRITE_OBB = "android.permission.WRITE_OBB";
+    field public static final String WRITE_SMS = "android.permission.WRITE_SMS";
   }
 
   public static final class Manifest.permission_group {
@@ -1058,8 +1063,8 @@
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_ADMIN_POLICY}) public java.util.List<java.lang.String> getPermittedInputMethodsForCurrentUser();
     method @Nullable public android.content.ComponentName getProfileOwner() throws java.lang.IllegalArgumentException;
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public String getProfileOwnerNameAsUser(int) throws java.lang.IllegalArgumentException;
-    method @NonNull public String getString(@NonNull String, @NonNull java.util.concurrent.Callable<java.lang.String>);
-    method @NonNull public String getString(@NonNull String, @NonNull java.util.concurrent.Callable<java.lang.String>, @NonNull java.lang.Object...);
+    method @Nullable public String getString(@NonNull String, @NonNull java.util.concurrent.Callable<java.lang.String>);
+    method @Nullable public String getString(@NonNull String, @NonNull java.util.concurrent.Callable<java.lang.String>, @NonNull java.lang.Object...);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public int getUserProvisioningState();
     method public boolean isDeviceManaged();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioned();
@@ -1195,6 +1200,7 @@
   public final class FullyManagedDeviceProvisioningParams implements android.os.Parcelable {
     method public boolean canDeviceOwnerGrantSensorsPermissions();
     method public int describeContents();
+    method @NonNull public android.os.PersistableBundle getAdminExtras();
     method @NonNull public android.content.ComponentName getDeviceAdminComponentName();
     method public long getLocalTime();
     method @Nullable public java.util.Locale getLocale();
@@ -1208,6 +1214,7 @@
   public static final class FullyManagedDeviceProvisioningParams.Builder {
     ctor public FullyManagedDeviceProvisioningParams.Builder(@NonNull android.content.ComponentName, @NonNull String);
     method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams build();
+    method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setAdminExtras(@NonNull android.os.PersistableBundle);
     method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setCanDeviceOwnerGrantSensorsPermissions(boolean);
     method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setLeaveAllSystemAppsEnabled(boolean);
     method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setLocalTime(long);
@@ -1218,6 +1225,7 @@
   public final class ManagedProfileProvisioningParams implements android.os.Parcelable {
     method public int describeContents();
     method @Nullable public android.accounts.Account getAccountToMigrate();
+    method @NonNull public android.os.PersistableBundle getAdminExtras();
     method @NonNull public String getOwnerName();
     method @NonNull public android.content.ComponentName getProfileAdminComponentName();
     method @Nullable public String getProfileName();
@@ -1232,6 +1240,7 @@
     ctor public ManagedProfileProvisioningParams.Builder(@NonNull android.content.ComponentName, @NonNull String);
     method @NonNull public android.app.admin.ManagedProfileProvisioningParams build();
     method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setAccountToMigrate(@Nullable android.accounts.Account);
+    method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setAdminExtras(@NonNull android.os.PersistableBundle);
     method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setKeepingAccountOnMigration(boolean);
     method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setLeaveAllSystemAppsEnabled(boolean);
     method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setOrganizationOwnedProvisioning(boolean);
@@ -2683,6 +2692,7 @@
     method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public android.content.Intent registerReceiverForAllUsers(@Nullable android.content.BroadcastReceiver, @NonNull android.content.IntentFilter, @Nullable String, @Nullable android.os.Handler, int);
     method public abstract void sendBroadcast(android.content.Intent, @Nullable String, @Nullable android.os.Bundle);
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public abstract void sendBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle, @Nullable String, @Nullable android.os.Bundle);
+    method public void sendBroadcastMultiplePermissions(@NonNull android.content.Intent, @NonNull String[], @Nullable android.app.BroadcastOptions);
     method public abstract void sendOrderedBroadcast(@NonNull android.content.Intent, @Nullable String, @Nullable android.os.Bundle, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle);
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public void startActivityAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.os.UserHandle);
     field public static final String AMBIENT_CONTEXT_SERVICE = "ambient_context";
@@ -2786,6 +2796,7 @@
     field public static final String ACTION_UPGRADE_SETUP = "android.intent.action.UPGRADE_SETUP";
     field public static final String ACTION_USER_ADDED = "android.intent.action.USER_ADDED";
     field public static final String ACTION_USER_REMOVED = "android.intent.action.USER_REMOVED";
+    field public static final String ACTION_USER_SWITCHED = "android.intent.action.USER_SWITCHED";
     field @RequiresPermission(android.Manifest.permission.START_VIEW_APP_FEATURES) public static final String ACTION_VIEW_APP_FEATURES = "android.intent.action.VIEW_APP_FEATURES";
     field public static final String ACTION_VOICE_ASSIST = "android.intent.action.VOICE_ASSIST";
     field public static final String CATEGORY_LEANBACK_SETTINGS = "android.intent.category.LEANBACK_SETTINGS";
@@ -2808,7 +2819,9 @@
     field public static final String EXTRA_ROLE_NAME = "android.intent.extra.ROLE_NAME";
     field public static final String EXTRA_SHOWING_ATTRIBUTION = "android.intent.extra.SHOWING_ATTRIBUTION";
     field public static final String EXTRA_UNKNOWN_INSTANT_APP = "android.intent.extra.UNKNOWN_INSTANT_APP";
+    field public static final String EXTRA_USER_HANDLE = "android.intent.extra.user_handle";
     field public static final String EXTRA_VERIFICATION_BUNDLE = "android.intent.extra.VERIFICATION_BUNDLE";
+    field public static final int FLAG_RECEIVER_INCLUDE_BACKGROUND = 16777216; // 0x1000000
     field public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 67108864; // 0x4000000
     field public static final String METADATA_SETUP_VERSION = "android.SETUP_VERSION";
   }
@@ -5884,6 +5897,7 @@
     field public static final int DEVICE_VOLUME_BEHAVIOR_VARIABLE = 0; // 0x0
     field public static final String EXTRA_VOLUME_STREAM_TYPE = "android.media.EXTRA_VOLUME_STREAM_TYPE";
     field public static final String EXTRA_VOLUME_STREAM_VALUE = "android.media.EXTRA_VOLUME_STREAM_VALUE";
+    field public static final int FLAG_BLUETOOTH_ABS_VOLUME = 64; // 0x40
     field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int STREAM_ASSISTANT = 11; // 0xb
     field public static final int STREAM_BLUETOOTH_SCO = 6; // 0x6
     field public static final int SUCCESS = 0; // 0x0
@@ -10230,6 +10244,7 @@
     method @Deprecated public static boolean checkAndNoteWriteSettingsOperation(@NonNull android.content.Context, int, @NonNull String, boolean);
     method public static boolean checkAndNoteWriteSettingsOperation(@NonNull android.content.Context, int, @NonNull String, @Nullable String, boolean);
     field public static final String ACTION_ACCESSIBILITY_DETAILS_SETTINGS = "android.settings.ACCESSIBILITY_DETAILS_SETTINGS";
+    field public static final String ACTION_BEDTIME_SETTINGS = "android.settings.BEDTIME_SETTINGS";
     field public static final String ACTION_BUGREPORT_HANDLER_SETTINGS = "android.settings.BUGREPORT_HANDLER_SETTINGS";
     field public static final String ACTION_ENTERPRISE_PRIVACY_SETTINGS = "android.settings.ENTERPRISE_PRIVACY_SETTINGS";
     field public static final String ACTION_LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS = "android.settings.LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS";
@@ -11045,7 +11060,7 @@
 
   public class GameService extends android.app.Service {
     ctor public GameService();
-    method public final void createGameSession(@IntRange(from=0) int);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_GAME_ACTIVITY) public final void createGameSession(@IntRange(from=0) int);
     method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent);
     method public void onConnected();
     method public void onDisconnected();
@@ -11059,7 +11074,7 @@
     method public void onCreate();
     method public void onDestroy();
     method public void onGameTaskFocusChanged(boolean);
-    method @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) public final boolean restartGame();
+    method @RequiresPermission(android.Manifest.permission.MANAGE_GAME_ACTIVITY) public final boolean restartGame();
     method public void setTaskOverlayView(@NonNull android.view.View, @NonNull android.view.ViewGroup.LayoutParams);
     method public void takeScreenshot(@NonNull java.util.concurrent.Executor, @NonNull android.service.games.GameSession.ScreenshotCallback);
   }
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 8234f03..5649c5f 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -128,6 +128,7 @@
         "android/os/IThermalStatusListener.aidl",
         "android/os/IThermalService.aidl",
         "android/os/IPowerManager.aidl",
+        "android/os/IWakeLockCallback.aidl",
     ],
 }
 
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 876e401..3289304 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1005,6 +1005,11 @@
         RemoteCallback finishCallback;
     }
 
+    static final class DumpResourcesData {
+        public ParcelFileDescriptor fd;
+        public RemoteCallback finishCallback;
+    }
+
     static final class UpdateCompatibilityData {
         String pkg;
         CompatibilityInfo info;
@@ -1316,6 +1321,20 @@
             sendMessage(H.SCHEDULE_CRASH, args, typeId);
         }
 
+        @Override
+        public void dumpResources(ParcelFileDescriptor fd, RemoteCallback callback) {
+            DumpResourcesData data = new DumpResourcesData();
+            try {
+                data.fd = fd.dup();
+                data.finishCallback = callback;
+                sendMessage(H.DUMP_RESOURCES, data, 0, 0, false /*async*/);
+            } catch (IOException e) {
+                Slog.w(TAG, "dumpResources failed", e);
+            } finally {
+                IoUtils.closeQuietly(fd);
+            }
+        }
+
         public void dumpActivity(ParcelFileDescriptor pfd, IBinder activitytoken,
                 String prefix, String[] args) {
             DumpComponentInfo data = new DumpComponentInfo();
@@ -2039,6 +2058,7 @@
         public static final int UPDATE_UI_TRANSLATION_STATE = 163;
         public static final int SET_CONTENT_CAPTURE_OPTIONS_CALLBACK = 164;
         public static final int DUMP_GFXINFO = 165;
+        public static final int DUMP_RESOURCES = 166;
 
         public static final int INSTRUMENT_WITHOUT_RESTART = 170;
         public static final int FINISH_INSTRUMENTATION_WITHOUT_RESTART = 171;
@@ -2092,6 +2112,7 @@
                     case INSTRUMENT_WITHOUT_RESTART: return "INSTRUMENT_WITHOUT_RESTART";
                     case FINISH_INSTRUMENTATION_WITHOUT_RESTART:
                         return "FINISH_INSTRUMENTATION_WITHOUT_RESTART";
+                    case DUMP_RESOURCES: return "DUMP_RESOURCES";
                 }
             }
             return Integer.toString(code);
@@ -2207,6 +2228,9 @@
                 case DUMP_HEAP:
                     handleDumpHeap((DumpHeapData) msg.obj);
                     break;
+                case DUMP_RESOURCES:
+                    handleDumpResources((DumpResourcesData) msg.obj);
+                    break;
                 case DUMP_ACTIVITY:
                     handleDumpActivity((DumpComponentInfo)msg.obj);
                     break;
@@ -4585,6 +4609,23 @@
         }
     }
 
+    private void handleDumpResources(DumpResourcesData info) {
+        final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
+        try {
+            PrintWriter pw = new FastPrintWriter(new FileOutputStream(
+                    info.fd.getFileDescriptor()));
+
+            Resources.dumpHistory(pw, "");
+            pw.flush();
+            if (info.finishCallback != null) {
+                info.finishCallback.sendResult(null);
+            }
+        } finally {
+            IoUtils.closeQuietly(info.fd);
+            StrictMode.setThreadPolicy(oldPolicy);
+        }
+    }
+
     private void handleDumpActivity(DumpComponentInfo info) {
         final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
         try {
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 0d1bc05..fdf37f6 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -2363,11 +2363,11 @@
             Manifest.permission.USE_BIOMETRIC,
             Manifest.permission.ACTIVITY_RECOGNITION,
             Manifest.permission.SMS_FINANCIAL_TRANSACTIONS,
-            Manifest.permission.READ_MEDIA_AUDIO,
+            null,
             null, // no permission for OP_WRITE_MEDIA_AUDIO
-            Manifest.permission.READ_MEDIA_VIDEO,
+            null,
             null, // no permission for OP_WRITE_MEDIA_VIDEO
-            Manifest.permission.READ_MEDIA_IMAGE,
+            null,
             null, // no permission for OP_WRITE_MEDIA_IMAGES
             null, // no permission for OP_LEGACY_STORAGE
             null, // no permission for OP_ACCESS_ACCESSIBILITY
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index f3315a8..8181a74 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1997,7 +1997,8 @@
 
     private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags,
             String instanceName, Handler handler, Executor executor, UserHandle user) {
-        // Keep this in sync with DevicePolicyManager.bindDeviceAdminServiceAsUser.
+        // Keep this in sync with DevicePolicyManager.bindDeviceAdminServiceAsUser and
+        // ActivityManagerService.LocalService.startAndBindSupplementalProcessService
         IServiceConnection sd;
         if (conn == null) {
             throw new IllegalArgumentException("connection is null");
@@ -2023,10 +2024,10 @@
                 flags |= BIND_WAIVE_PRIORITY;
             }
             service.prepareToLeaveProcess(this);
-            int res = ActivityManager.getService().bindIsolatedService(
-                mMainThread.getApplicationThread(), getActivityToken(), service,
-                service.resolveTypeIfNeeded(getContentResolver()),
-                sd, flags, instanceName, getOpPackageName(), user.getIdentifier());
+            int res = ActivityManager.getService().bindServiceInstance(
+                    mMainThread.getApplicationThread(), getActivityToken(), service,
+                    service.resolveTypeIfNeeded(getContentResolver()),
+                    sd, flags, instanceName, getOpPackageName(), user.getIdentifier());
             if (res < 0) {
                 throw new SecurityException(
                         "Not allowed to bind to service " + service);
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 82c419a..e4ef12c 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -169,7 +169,7 @@
     int bindService(in IApplicationThread caller, in IBinder token, in Intent service,
             in String resolvedType, in IServiceConnection connection, int flags,
             in String callingPackage, int userId);
-    int bindIsolatedService(in IApplicationThread caller, in IBinder token, in Intent service,
+    int bindServiceInstance(in IApplicationThread caller, in IBinder token, in Intent service,
             in String resolvedType, in IServiceConnection connection, int flags,
             in String instanceName, in String callingPackage, int userId);
     void updateServiceGroup(in IServiceConnection connection, int group, int importance);
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 1714229..77657d5 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -113,6 +113,7 @@
             in ParcelFileDescriptor fd, in RemoteCallback finishCallback);
     void dumpActivity(in ParcelFileDescriptor fd, IBinder servicetoken, in String prefix,
             in String[] args);
+    void dumpResources(in ParcelFileDescriptor fd, in RemoteCallback finishCallback);
     void clearDnsCache();
     void updateHttpProxy();
     void setCoreSettings(in Bundle coreSettings);
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 14afd0f..5d1f4df 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -746,7 +746,7 @@
         if (!hasPermission(Manifest.permission.SET_INITIAL_LOCK)) {
             throw new SecurityException("Requires SET_INITIAL_LOCK permission.");
         }
-        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+        return true;
     }
 
     private boolean hasPermission(String permission) {
@@ -814,6 +814,8 @@
     /**
     * Set the lockscreen password after validating against its expected complexity level.
     *
+    * Below {@link android.os.Build.VERSION_CODES#S_V2}, this API will only work
+    * when {@link PackageManager.FEATURE_AUTOMOTIVE} is present.
     * @param lockType - type of lock as specified in {@link LockTypes}
     * @param password - password to validate; this has the same encoding
     *        as the output of String#getBytes
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 18f9379..3d2c03d 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -214,6 +214,12 @@
     public boolean topActivityInSizeCompat;
 
     /**
+     * Whether the direct top activity is eligible for letterbox education.
+     * @hide
+     */
+    public boolean topActivityEligibleForLetterboxEducation;
+
+    /**
      * Whether this task is resizable. Unlike {@link #resizeMode} (which is what the top activity
      * supports), this is what the system actually uses for resizability based on other policy and
      * developer options.
@@ -398,7 +404,8 @@
 
     /** @hide */
     public boolean hasCompatUI() {
-        return hasCameraCompatControl() || topActivityInSizeCompat;
+        return hasCameraCompatControl() || topActivityInSizeCompat
+                || topActivityEligibleForLetterboxEducation;
     }
 
     /**
@@ -460,6 +467,8 @@
         return displayId == that.displayId
                 && taskId == that.taskId
                 && topActivityInSizeCompat == that.topActivityInSizeCompat
+                && topActivityEligibleForLetterboxEducation
+                    == that.topActivityEligibleForLetterboxEducation
                 && cameraCompatControlState == that.cameraCompatControlState
                 // Bounds are important if top activity has compat controls.
                 && (!hasCompatUI() || configuration.windowConfiguration.getBounds()
@@ -507,6 +516,7 @@
         isVisible = source.readBoolean();
         isSleeping = source.readBoolean();
         topActivityInSizeCompat = source.readBoolean();
+        topActivityEligibleForLetterboxEducation = source.readBoolean();
         mTopActivityLocusId = source.readTypedObject(LocusId.CREATOR);
         displayAreaFeatureId = source.readInt();
         cameraCompatControlState = source.readInt();
@@ -551,6 +561,7 @@
         dest.writeBoolean(isVisible);
         dest.writeBoolean(isSleeping);
         dest.writeBoolean(topActivityInSizeCompat);
+        dest.writeBoolean(topActivityEligibleForLetterboxEducation);
         dest.writeTypedObject(mTopActivityLocusId, flags);
         dest.writeInt(displayAreaFeatureId);
         dest.writeInt(cameraCompatControlState);
@@ -585,6 +596,8 @@
                 + " isVisible=" + isVisible
                 + " isSleeping=" + isSleeping
                 + " topActivityInSizeCompat=" + topActivityInSizeCompat
+                + " topActivityEligibleForLetterboxEducation= "
+                        + topActivityEligibleForLetterboxEducation
                 + " locusId=" + mTopActivityLocusId
                 + " displayAreaFeatureId=" + displayAreaFeatureId
                 + " cameraCompatControlState="
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 9ac4030..a4227a4 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -633,7 +633,7 @@
      * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} to signal that an update
      * to the role holder is required.
      *
-     * <p>This result code must be accompanied by {@link #EXTRA_ROLE_HOLDER_STATE}.
+     * <p>This result code can be accompanied by {@link #EXTRA_ROLE_HOLDER_STATE}.
      *
      * @hide
      */
@@ -641,15 +641,19 @@
     public static final int RESULT_UPDATE_ROLE_HOLDER = 2;
 
     /**
-     * A {@link Bundle} extra which describes the state of the role holder at the time when it
-     * returns {@link #RESULT_UPDATE_ROLE_HOLDER}.
+     * A {@link PersistableBundle} extra which the role holder can use to describe its own state
+     * when it returns {@link #RESULT_UPDATE_ROLE_HOLDER}.
      *
-     * <p>After the update completes, the role holder's {@link
-     * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE} or {@link
+     * <p>If {@link #RESULT_UPDATE_ROLE_HOLDER} was accompanied by this extra, after the update
+     * completes, the role holder's {@link #ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE} or {@link
      * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} intent will be relaunched,
      * which will contain this extra. It is the role holder's responsibility to restore its
      * state from this extra.
      *
+     * <p>The content of this {@link PersistableBundle} is entirely up to the role holder. It
+     * should contain anything the role holder needs to restore its original state when it gets
+     * restarted.
+     *
      * @hide
      */
     @SystemApi
@@ -14815,6 +14819,11 @@
      * The device may not connect to networks that do not meet the minimum security level.
      * If the current network does not meet the minimum security level set, it will be disconnected.
      *
+     * The following shows the Wi-Fi security levels from the lowest to the highest security level:
+     * {@link #WIFI_SECURITY_OPEN}
+     * {@link #WIFI_SECURITY_PERSONAL}
+     * {@link #WIFI_SECURITY_ENTERPRISE_EAP}
+     * {@link #WIFI_SECURITY_ENTERPRISE_192}
      *
      * @param level minimum security level
      * @throws SecurityException if the caller is not a device owner or a profile owner on
@@ -14991,8 +15000,8 @@
      * <p>Also returns the drawable from {@code defaultDrawableLoader} if
      * {@link DevicePolicyResources.Drawables#UNDEFINED} was passed.
      *
-     * <p>{@code defaultDrawableLoader} must return a non {@code null} {@link Drawable}, otherwise a
-     * {@link NullPointerException} is thrown.
+     * <p>Calls to this API will not return {@code null} unless no updated drawable was found
+     * and the call to {@code defaultDrawableLoader} returned {@code null}.
      *
      * <p>This API uses the screen density returned from {@link Resources#getConfiguration()}, to
      * set a different value use
@@ -15009,7 +15018,7 @@
      * @param defaultDrawableLoader To get the default drawable if no updated drawable was set for
      *                              the provided params.
      */
-    @NonNull
+    @Nullable
     public Drawable getDrawable(
             @NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId,
             @NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle,
@@ -15025,8 +15034,8 @@
      * {@link #getDrawable(String, String, Callable)}
      * if an override was set for that specific source.
      *
-     * <p>{@code defaultDrawableLoader} must return a non {@code null} {@link Drawable}, otherwise a
-     * {@link NullPointerException} is thrown.
+     * <p>Calls to this API will not return {@code null} unless no updated drawable was found
+     * and the call to {@code defaultDrawableLoader} returned {@code null}.
      *
      * <p>Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get
      * notified when a resource has been updated.
@@ -15037,7 +15046,7 @@
      * @param defaultDrawableLoader To get the default drawable if no updated drawable was set for
      *                              the provided params.
      */
-    @NonNull
+    @Nullable
     public Drawable getDrawable(
             @NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId,
             @NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle,
@@ -15080,8 +15089,8 @@
      * Similar to {@link #getDrawable(String, String, Callable)}, but also accepts
      * {@code density}. See {@link Resources#getDrawableForDensity(int, int, Resources.Theme)}.
      *
-     * <p>{@code defaultDrawableLoader} must return a non {@code null} {@link Drawable}, otherwise a
-     * {@link NullPointerException} is thrown.
+     * <p>Calls to this API will not return {@code null} unless no updated drawable was found
+     * and the call to {@code defaultDrawableLoader} returned {@code null}.
      *
      * <p>Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get
      * notified when a resource has been updated.
@@ -15094,7 +15103,7 @@
      * @param defaultDrawableLoader To get the default drawable if no updated drawable was set for
      *                              the provided params.
      */
-    @NonNull
+    @Nullable
     public Drawable getDrawableForDensity(
             @NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId,
             @NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle,
@@ -15112,8 +15121,8 @@
      * Similar to {@link #getDrawable(String, String, String, Callable)}, but also accepts
      * {@code density}. See {@link Resources#getDrawableForDensity(int, int, Resources.Theme)}.
      *
-     * <p>{@code defaultDrawableLoader} must return a non {@code null} {@link Drawable}, otherwise a
-     * {@link NullPointerException} is thrown.
+      * <p>Calls to this API will not return {@code null} unless no updated drawable was found
+      * and the call to {@code defaultDrawableLoader} returned {@code null}.
      *
      * <p>Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get
      * notified when a resource has been updated.
@@ -15127,7 +15136,7 @@
      * @param defaultDrawableLoader To get the default drawable if no updated drawable was set for
      *                              the provided params.
      */
-    @NonNull
+    @Nullable
     public Drawable getDrawableForDensity(
             @NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId,
             @NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle,
@@ -15166,7 +15175,7 @@
     /**
      * For each {@link DevicePolicyStringResource} item in {@code strings}, it updates the string
      * resource for {@link DevicePolicyStringResource#getStringId()} to the string with ID
-     * {@code callingPackageResourceId} (see {@link DevicePolicyResources.String}), meaning any
+     * {@code callingPackageResourceId} (see {@link DevicePolicyResources.Strings}), meaning any
      * system UI surface calling {@link #getString} with {@code stringId} will get
      * the new resource after this API is called.
      *
@@ -15202,7 +15211,7 @@
 
     /**
      * Removes the updated strings for the list of {@code stringIds} (see
-     * {@link DevicePolicyResources.String}) that was previously set by calling {@link #setStrings},
+     * {@link DevicePolicyResources.Strings}) that was previously set by calling {@link #setStrings},
      * meaning any subsequent calls to {@link #getString} for the provided IDs will
      * return the default string from {@code defaultStringLoader}.
      *
@@ -15227,14 +15236,14 @@
 
     /**
      * Returns the appropriate updated string for the {@code stringId} (see
-     * {@link DevicePolicyResources.String}) if one was set using
+     * {@link DevicePolicyResources.Strings}) if one was set using
      * {@link #setStrings}, otherwise returns the string from {@code defaultStringLoader}.
      *
      * <p>Also returns the string from {@code defaultStringLoader} if
-     * {@link DevicePolicyResources.String#INVALID_ID} was passed.
+     * {@link DevicePolicyResources.Strings#UNDEFINED} was passed.
      *
-     * <p>{@code defaultStringLoader} must return a non {@code null} {@link String}, otherwise a
-     * {@link NullPointerException} is thrown.
+     * <p>Calls to this API will not return {@code null} unless no updated drawable was found
+     * and the call to {@code defaultStringLoader} returned {@code null}.
      *
      * <p>Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get
      * notified when a resource has been updated.
@@ -15249,7 +15258,7 @@
      * @hide
      */
     @SystemApi
-    @NonNull
+    @Nullable
     public String getString(
             @NonNull @DevicePolicyResources.UpdatableStringId String stringId,
             @NonNull Callable<String> defaultStringLoader) {
@@ -15284,8 +15293,8 @@
      * {@link java.util.Formatter} and {@link java.lang.String#format}, (see
      * {@link Resources#getString(int, Object...)}).
      *
-     * <p>{@code defaultStringLoader} must return a non {@code null} {@link String}, otherwise a
-     * {@link NullPointerException} is thrown.
+     * <p>Calls to this API will not return {@code null} unless no updated drawable was found
+     * and the call to {@code defaultStringLoader} returned {@code null}.
      *
      * @param stringId The IDs to get the updated resource for.
      * @param defaultStringLoader To get the default string if no updated string was set for
@@ -15295,7 +15304,7 @@
      * @hide
      */
     @SystemApi
-    @NonNull
+    @Nullable
     @SuppressLint("SamShouldBeLast")
     public String getString(
             @NonNull @DevicePolicyResources.UpdatableStringId String stringId,
diff --git a/core/java/android/app/admin/DevicePolicyResources.java b/core/java/android/app/admin/DevicePolicyResources.java
index 2ad2010..cf349b0 100644
--- a/core/java/android/app/admin/DevicePolicyResources.java
+++ b/core/java/android/app/admin/DevicePolicyResources.java
@@ -93,6 +93,167 @@
 import static android.app.admin.DevicePolicyResources.Strings.MediaProvider.SWITCH_TO_PERSONAL_MESSAGE;
 import static android.app.admin.DevicePolicyResources.Strings.MediaProvider.SWITCH_TO_WORK_MESSAGE;
 import static android.app.admin.DevicePolicyResources.Strings.MediaProvider.WORK_PROFILE_PAUSED_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ACCESSIBILITY_CATEGORY_PERSONAL;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ACCESSIBILITY_CATEGORY_WORK;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ACCESSIBILITY_PERSONAL_ACCOUNT_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ACCESSIBILITY_WORK_ACCOUNT_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ACCOUNTS_SEARCH_KEYWORDS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ACTIVATE_DEVICE_ADMIN_APP;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ACTIVATE_DEVICE_ADMIN_APP_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ACTIVATE_THIS_DEVICE_ADMIN_APP;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ACTIVE_DEVICE_ADMIN_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTIONS_APPS_COUNT;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTIONS_APPS_COUNT_MINIMUM;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_ACCESS_CAMERA;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_ACCESS_LOCATION;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_ACCESS_MICROPHONE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_APPS_COUNT_ESTIMATED;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_APPS_INSTALLED;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_NONE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_SET_CURRENT_INPUT_METHOD;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_SET_DEFAULT_APPS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_SET_HTTP_PROXY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_SET_INPUT_METHOD_NAME;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CAN_LOCK_DEVICE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CAN_SEE_APPS_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CAN_SEE_BUG_REPORT_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CAN_SEE_SECURITY_LOGS_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CAN_SEE_NETWORK_LOGS_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CAN_SEE_USAGE_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CAN_SEE_WORK_DATA_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CAN_WIPE_DEVICE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_DEVICE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_WORK_PROFILE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ALWAYS_ON_VPN_DEVICE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ALWAYS_ON_VPN_PERSONAL_PROFILE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ALWAYS_ON_VPN_WORK_PROFILE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.APP_CAN_ACCESS_PERSONAL_DATA;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.APP_CAN_ACCESS_PERSONAL_PERMISSIONS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CA_CERTS_DEVICE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CA_CERTS_PERSONAL_PROFILE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CA_CERTS_WORK_PROFILE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CHANGES_MADE_BY_YOUR_ORGANIZATION_ADMIN_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONFIRM_WORK_PROFILE_PASSWORD_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONFIRM_WORK_PROFILE_PATTERN_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONFIRM_WORK_PROFILE_PIN_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONNECTED_APPS_SEARCH_KEYWORDS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONNECTED_APPS_SHARE_PERMISSIONS_AND_DATA;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONNECTED_WORK_AND_PERSONAL_APPS_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONNECT_APPS_DIALOG_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONNECT_APPS_DIALOG_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONTACT_YOUR_IT_ADMIN;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONTROLLED_BY_ADMIN_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CROSS_PROFILE_CALENDAR_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CROSS_PROFILE_CALENDAR_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.DEVICE_ADMIN_POLICIES_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.DEVICE_ADMIN_SETTINGS_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.DEVICE_MANAGED_WITHOUT_NAME;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.DEVICE_MANAGED_WITH_NAME;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.DEVICE_OWNER_INSTALLED_CERTIFICATE_AUTHORITY_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.DISABLED_BY_IT_ADMIN_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ENTERPRISE_PRIVACY_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ERROR_MOVE_DEVICE_ADMIN;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.FACE_SETTINGS_FOR_WORK_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.FACE_UNLOCK_DISABLED;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.FINGERPRINT_FOR_WORK;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.FINGERPRINT_UNLOCK_DISABLED;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.FINGERPRINT_UNLOCK_DISABLED_EXPLANATION;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.FORGOT_PASSWORD_TEXT;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.FORGOT_PASSWORD_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.HOW_TO_DISCONNECT_APPS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.INFORMATION_YOUR_ORGANIZATION_CAN_SEE_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.INSTALL_IN_PERSONAL_PROFILE_TO_CONNECT_PROMPT;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.INSTALL_IN_WORK_PROFILE_TO_CONNECT_PROMPT;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.IT_ADMIN_POLICY_DISABLING_INFO_URL;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.MANAGED_BY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.MANAGED_DEVICE_INFO;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.MANAGED_DEVICE_INFO_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.MANAGED_DEVICE_INFO_SUMMARY_WITH_NAME;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.MANAGED_PROFILE_SETTINGS_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.MANAGE_DEVICE_ADMIN_APPS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.NEW_DEVICE_ADMIN_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.NEW_DEVICE_ADMIN_WARNING_SIMPLIFIED;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.NO_DEVICE_ADMINS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.NUMBER_OF_DEVICE_ADMINS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.NUMBER_OF_DEVICE_ADMINS_NONE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ONLY_CONNECT_TRUSTED_APPS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.OTHER_OPTIONS_DISABLED_BY_ADMIN;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.PASSWORD_RECENTLY_USED;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.PERSONAL_CATEGORY_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.PERSONAL_PROFILE_APP_SUBTEXT;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.PIN_RECENTLY_USED;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.REENTER_WORK_PROFILE_PASSWORD_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.REENTER_WORK_PROFILE_PIN_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.REMOVE_ACCOUNT_FAILED_ADMIN_RESTRICTION;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.REMOVE_AND_UNINSTALL_DEVICE_ADMIN;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.REMOVE_DEVICE_ADMIN;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.REMOVE_WORK_PROFILE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SELECT_DEVICE_ADMIN_APPS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SET_PROFILE_OWNER_DIALOG_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SET_PROFILE_OWNER_POSTSETUP_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SET_PROFILE_OWNER_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SET_WORK_PROFILE_PASSWORD_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SET_WORK_PROFILE_PATTERN_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SET_WORK_PROFILE_PIN_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SHARE_REMOTE_BUGREPORT_DIALOG_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SHARE_REMOTE_BUGREPORT_FINISHED_REQUEST_CONSENT;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SHARE_REMOTE_BUGREPORT_NOT_FINISHED_REQUEST_CONSENT;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SHARING_REMOTE_BUGREPORT_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.UNINSTALL_DEVICE_ADMIN;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.USER_ADMIN_POLICIES_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_APPS_CANNOT_ACCESS_NOTIFICATION_SETTINGS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_CATEGORY_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_ADMIN_POLICIES_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_ALARM_RINGTONE_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_APP_SUBTEXT;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONFIRM_PATTERN;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONFIRM_PIN;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONFIRM_REMOVE_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONFIRM_REMOVE_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONTACT_SEARCH_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONTACT_SEARCH_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_DISABLE_USAGE_ACCESS_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_FINGERPRINT_LAST_DELETE_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_INSTALLED_CERTIFICATE_AUTHORITY_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_IT_ADMIN_CANT_RESET_SCREEN_LOCK;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_KEYBOARDS_AND_TOOLS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LAST_PASSWORD_ATTEMPT_BEFORE_WIPE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LAST_PATTERN_ATTEMPT_BEFORE_WIPE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LAST_PIN_ATTEMPT_BEFORE_WIPE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LOCATION_SWITCH_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LOCKED_NOTIFICATION_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LOCK_ATTEMPTS_FAILED;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_MANAGED_BY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_NOTIFICATIONS_SECTION_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_NOTIFICATION_LISTENER_BLOCKED;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_NOTIFICATION_RINGTONE_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_NOT_AVAILABLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_OFF_CONDITION_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_PASSWORD_REQUIRED;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_PRIVACY_POLICY_INFO;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_PRIVACY_POLICY_INFO_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_RINGTONE_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SCREEN_LOCK_SETUP_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SECURITY_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SETTING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SETTING_OFF_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SETTING_ON_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SET_UNLOCK_LAUNCH_PICKER_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SOUND_SETTINGS_SECTION_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_ACTIVE_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_UNIFICATION_SEARCH_KEYWORDS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_UNIFY_LOCKS_DETAIL;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_UNIFY_LOCKS_NONCOMPLIANT;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_UNIFY_LOCKS_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_UNIFY_LOCKS_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_USER_LABEL;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_USE_PERSONAL_SOUNDS_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_USE_PERSONAL_SOUNDS_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.YOUR_ACCESS_TO_THIS_DEVICE_TITLE;
 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS;
 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT;
 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PATTERN_LAST_ATTEMPT;
@@ -159,7 +320,8 @@
             Drawables.WORK_PROFILE_OFF_ICON,
             Drawables.WORK_PROFILE_USER_ICON
     })
-    public @interface UpdatableDrawableId {}
+    public @interface UpdatableDrawableId {
+    }
 
     /**
      * Identifiers to specify the desired style for the updatable device management system
@@ -174,7 +336,8 @@
             Drawables.Style.OUTLINE,
             Drawables.Style.DEFAULT
     })
-    public @interface UpdatableDrawableStyle {}
+    public @interface UpdatableDrawableStyle {
+    }
 
     /**
      * Identifiers to specify the location if the updatable device management system resource.
@@ -191,7 +354,8 @@
             Drawables.Source.QUICK_SETTINGS,
             Drawables.Source.STATUS_BAR
     })
-    public @interface UpdatableDrawableSource {}
+    public @interface UpdatableDrawableSource {
+    }
 
     /**
      * Resource identifiers used to update device management-related string resources.
@@ -231,7 +395,7 @@
             PERSONAL_APP_SUSPENSION_TITLE, PERSONAL_APP_SUSPENSION_MESSAGE,
             PERSONAL_APP_SUSPENSION_SOON_MESSAGE, PERSONAL_APP_SUSPENSION_TURN_ON_PROFILE,
             PRINTING_DISABLED_NAMED_ADMIN, LOCATION_CHANGED_TITLE, LOCATION_CHANGED_MESSAGE,
-            NETWORK_LOGGING_TITLE,  NETWORK_LOGGING_MESSAGE,
+            NETWORK_LOGGING_TITLE, NETWORK_LOGGING_MESSAGE,
             NOTIFICATION_WORK_PROFILE_CONTENT_DESCRIPTION, NOTIFICATION_CHANNEL_DEVICE_ADMIN,
             SWITCH_TO_WORK_LABEL, SWITCH_TO_PERSONAL_LABEL, FORWARD_INTENT_TO_WORK,
             FORWARD_INTENT_TO_PERSONAL, RESOLVER_WORK_PROFILE_NOT_SUPPORTED, RESOLVER_PERSONAL_TAB,
@@ -257,7 +421,86 @@
             SWITCH_TO_WORK_MESSAGE, SWITCH_TO_PERSONAL_MESSAGE, BLOCKED_BY_ADMIN_TITLE,
             BLOCKED_FROM_PERSONAL_MESSAGE, BLOCKED_FROM_PERSONAL_MESSAGE,
             BLOCKED_FROM_WORK_MESSAGE, Strings.MediaProvider.WORK_PROFILE_PAUSED_TITLE,
-            WORK_PROFILE_PAUSED_MESSAGE
+            WORK_PROFILE_PAUSED_MESSAGE,
+
+            // Settings Strings
+            FACE_SETTINGS_FOR_WORK_TITLE, WORK_PROFILE_FINGERPRINT_LAST_DELETE_MESSAGE,
+            WORK_PROFILE_IT_ADMIN_CANT_RESET_SCREEN_LOCK, WORK_PROFILE_SCREEN_LOCK_SETUP_MESSAGE,
+            WORK_PROFILE_SET_UNLOCK_LAUNCH_PICKER_TITLE,
+            WORK_PROFILE_LAST_PATTERN_ATTEMPT_BEFORE_WIPE,
+            WORK_PROFILE_LAST_PIN_ATTEMPT_BEFORE_WIPE,
+            WORK_PROFILE_LAST_PASSWORD_ATTEMPT_BEFORE_WIPE, WORK_PROFILE_LOCK_ATTEMPTS_FAILED,
+            ACCESSIBILITY_CATEGORY_WORK, ACCESSIBILITY_CATEGORY_PERSONAL,
+            ACCESSIBILITY_WORK_ACCOUNT_TITLE, ACCESSIBILITY_PERSONAL_ACCOUNT_TITLE,
+            WORK_PROFILE_LOCATION_SWITCH_TITLE, SET_WORK_PROFILE_PASSWORD_HEADER,
+            SET_WORK_PROFILE_PIN_HEADER, SET_WORK_PROFILE_PATTERN_HEADER,
+            CONFIRM_WORK_PROFILE_PASSWORD_HEADER, CONFIRM_WORK_PROFILE_PIN_HEADER,
+            CONFIRM_WORK_PROFILE_PATTERN_HEADER, REENTER_WORK_PROFILE_PASSWORD_HEADER,
+            REENTER_WORK_PROFILE_PIN_HEADER, WORK_PROFILE_CONFIRM_PATTERN, WORK_PROFILE_CONFIRM_PIN,
+            WORK_PROFILE_PASSWORD_REQUIRED, WORK_PROFILE_SECURITY_TITLE,
+            WORK_PROFILE_UNIFY_LOCKS_TITLE, WORK_PROFILE_UNIFY_LOCKS_SUMMARY,
+            WORK_PROFILE_UNIFY_LOCKS_DETAIL, WORK_PROFILE_UNIFY_LOCKS_NONCOMPLIANT,
+            WORK_PROFILE_KEYBOARDS_AND_TOOLS, WORK_PROFILE_NOT_AVAILABLE, WORK_PROFILE_SETTING,
+            WORK_PROFILE_SETTING_ON_SUMMARY, WORK_PROFILE_SETTING_OFF_SUMMARY, REMOVE_WORK_PROFILE,
+            DEVICE_OWNER_INSTALLED_CERTIFICATE_AUTHORITY_WARNING,
+            WORK_PROFILE_INSTALLED_CERTIFICATE_AUTHORITY_WARNING, WORK_PROFILE_CONFIRM_REMOVE_TITLE,
+            WORK_PROFILE_CONFIRM_REMOVE_MESSAGE, WORK_APPS_CANNOT_ACCESS_NOTIFICATION_SETTINGS,
+            WORK_PROFILE_SOUND_SETTINGS_SECTION_HEADER, WORK_PROFILE_USE_PERSONAL_SOUNDS_TITLE,
+            WORK_PROFILE_USE_PERSONAL_SOUNDS_SUMMARY, WORK_PROFILE_RINGTONE_TITLE,
+            WORK_PROFILE_NOTIFICATION_RINGTONE_TITLE, WORK_PROFILE_ALARM_RINGTONE_TITLE,
+            WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_ACTIVE_SUMMARY,
+            ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_TITLE,
+            ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_MESSAGE,
+            WORK_PROFILE_NOTIFICATIONS_SECTION_HEADER, WORK_PROFILE_LOCKED_NOTIFICATION_TITLE,
+            WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_TITLE,
+            WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_SUMMARY,
+            WORK_PROFILE_NOTIFICATION_LISTENER_BLOCKED, CONNECTED_WORK_AND_PERSONAL_APPS_TITLE,
+            CONNECTED_APPS_SHARE_PERMISSIONS_AND_DATA, ONLY_CONNECT_TRUSTED_APPS,
+            HOW_TO_DISCONNECT_APPS, CONNECT_APPS_DIALOG_TITLE, CONNECT_APPS_DIALOG_SUMMARY,
+            APP_CAN_ACCESS_PERSONAL_DATA, APP_CAN_ACCESS_PERSONAL_PERMISSIONS,
+            INSTALL_IN_WORK_PROFILE_TO_CONNECT_PROMPT,
+            INSTALL_IN_PERSONAL_PROFILE_TO_CONNECT_PROMPT, WORK_PROFILE_MANAGED_BY, MANAGED_BY,
+            WORK_PROFILE_DISABLE_USAGE_ACCESS_WARNING, DISABLED_BY_IT_ADMIN_TITLE,
+            CONTACT_YOUR_IT_ADMIN, WORK_PROFILE_ADMIN_POLICIES_WARNING, USER_ADMIN_POLICIES_WARNING,
+            DEVICE_ADMIN_POLICIES_WARNING, WORK_PROFILE_OFF_CONDITION_TITLE,
+            MANAGED_PROFILE_SETTINGS_TITLE, WORK_PROFILE_CONTACT_SEARCH_TITLE,
+            WORK_PROFILE_CONTACT_SEARCH_SUMMARY, CROSS_PROFILE_CALENDAR_TITLE,
+            CROSS_PROFILE_CALENDAR_SUMMARY, ALWAYS_ON_VPN_PERSONAL_PROFILE, ALWAYS_ON_VPN_DEVICE,
+            ALWAYS_ON_VPN_WORK_PROFILE, CA_CERTS_PERSONAL_PROFILE, CA_CERTS_WORK_PROFILE,
+            CA_CERTS_DEVICE, ADMIN_CAN_LOCK_DEVICE, ADMIN_CAN_WIPE_DEVICE,
+            ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_DEVICE,
+            ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_WORK_PROFILE, DEVICE_MANAGED_WITHOUT_NAME,
+            DEVICE_MANAGED_WITH_NAME, WORK_PROFILE_APP_SUBTEXT, PERSONAL_PROFILE_APP_SUBTEXT,
+            FINGERPRINT_FOR_WORK, FACE_UNLOCK_DISABLED, FINGERPRINT_UNLOCK_DISABLED,
+            FINGERPRINT_UNLOCK_DISABLED_EXPLANATION, PIN_RECENTLY_USED, PASSWORD_RECENTLY_USED,
+            MANAGE_DEVICE_ADMIN_APPS, NUMBER_OF_DEVICE_ADMINS_NONE, NUMBER_OF_DEVICE_ADMINS,
+            FORGOT_PASSWORD_TITLE, FORGOT_PASSWORD_TEXT, ERROR_MOVE_DEVICE_ADMIN,
+            DEVICE_ADMIN_SETTINGS_TITLE, REMOVE_DEVICE_ADMIN, UNINSTALL_DEVICE_ADMIN,
+            REMOVE_AND_UNINSTALL_DEVICE_ADMIN, SELECT_DEVICE_ADMIN_APPS, NO_DEVICE_ADMINS,
+            ACTIVATE_DEVICE_ADMIN_APP, ACTIVATE_THIS_DEVICE_ADMIN_APP,
+            ACTIVATE_DEVICE_ADMIN_APP_TITLE, NEW_DEVICE_ADMIN_WARNING,
+            NEW_DEVICE_ADMIN_WARNING_SIMPLIFIED, ACTIVE_DEVICE_ADMIN_WARNING,
+            SET_PROFILE_OWNER_TITLE, SET_PROFILE_OWNER_DIALOG_TITLE,
+            SET_PROFILE_OWNER_POSTSETUP_WARNING, OTHER_OPTIONS_DISABLED_BY_ADMIN,
+            REMOVE_ACCOUNT_FAILED_ADMIN_RESTRICTION, IT_ADMIN_POLICY_DISABLING_INFO_URL,
+            SHARE_REMOTE_BUGREPORT_DIALOG_TITLE, SHARE_REMOTE_BUGREPORT_FINISHED_REQUEST_CONSENT,
+            SHARE_REMOTE_BUGREPORT_NOT_FINISHED_REQUEST_CONSENT, SHARING_REMOTE_BUGREPORT_MESSAGE,
+            MANAGED_DEVICE_INFO, MANAGED_DEVICE_INFO_SUMMARY, MANAGED_DEVICE_INFO_SUMMARY_WITH_NAME,
+            ENTERPRISE_PRIVACY_HEADER, INFORMATION_YOUR_ORGANIZATION_CAN_SEE_TITLE,
+            CHANGES_MADE_BY_YOUR_ORGANIZATION_ADMIN_TITLE, YOUR_ACCESS_TO_THIS_DEVICE_TITLE,
+            ADMIN_CAN_SEE_WORK_DATA_WARNING, ADMIN_CAN_SEE_APPS_WARNING,
+            ADMIN_CAN_SEE_USAGE_WARNING, ADMIN_CAN_SEE_NETWORK_LOGS_WARNING,
+            ADMIN_CAN_SEE_BUG_REPORT_WARNING, ADMIN_CAN_SEE_SECURITY_LOGS_WARNING,
+            ADMIN_ACTION_NONE, ADMIN_ACTION_APPS_INSTALLED, ADMIN_ACTION_APPS_COUNT_ESTIMATED,
+            ADMIN_ACTIONS_APPS_COUNT_MINIMUM, ADMIN_ACTION_ACCESS_LOCATION,
+            ADMIN_ACTION_ACCESS_MICROPHONE, ADMIN_ACTION_ACCESS_CAMERA,
+            ADMIN_ACTION_SET_DEFAULT_APPS, ADMIN_ACTIONS_APPS_COUNT,
+            ADMIN_ACTION_SET_CURRENT_INPUT_METHOD, ADMIN_ACTION_SET_INPUT_METHOD_NAME,
+            ADMIN_ACTION_SET_HTTP_PROXY, WORK_PROFILE_PRIVACY_POLICY_INFO_SUMMARY,
+            WORK_PROFILE_PRIVACY_POLICY_INFO, CONNECTED_APPS_SEARCH_KEYWORDS,
+            WORK_PROFILE_UNIFICATION_SEARCH_KEYWORDS, ACCOUNTS_SEARCH_KEYWORDS,
+            CONTROLLED_BY_ADMIN_SUMMARY, WORK_PROFILE_USER_LABEL, WORK_CATEGORY_HEADER,
+            PERSONAL_CATEGORY_HEADER
     })
     public @interface UpdatableStringId {
     }
@@ -432,7 +675,8 @@
     @SystemApi
     public static final class Strings {
 
-        private Strings() {}
+        private Strings() {
+        }
 
         /**
          * An ID for any string that can't be updated.
@@ -456,13 +700,1209 @@
 
         /**
          * Class containing the identifiers used to update device management-related system strings
+         * in the Settings package
+         *
+         * @hide
+         */
+        public static final class Settings {
+
+            private Settings() {
+            }
+
+            private static final String PREFIX = "Settings.";
+
+            /**
+             * Title shown for menu item that launches face settings or enrollment, for work profile
+             */
+            public static final String FACE_SETTINGS_FOR_WORK_TITLE =
+                    PREFIX + "FACE_SETTINGS_FOR_WORK_TITLE";
+
+            /**
+             * Warning when removing the last fingerprint on a work profile
+             */
+            public static final String WORK_PROFILE_FINGERPRINT_LAST_DELETE_MESSAGE =
+                    PREFIX + "WORK_PROFILE_FINGERPRINT_LAST_DELETE_MESSAGE";
+
+            /**
+             * Text letting the user know that their IT admin can't reset their screen lock if they
+             * forget it, and they can choose to set another lock that would be specifically for
+             * their work apps
+             */
+            public static final String WORK_PROFILE_IT_ADMIN_CANT_RESET_SCREEN_LOCK =
+                    PREFIX + "WORK_PROFILE_IT_ADMIN_CANT_RESET_SCREEN_LOCK";
+
+            /**
+             * Message shown in screen lock picker for setting up a work profile screen lock
+             */
+            public static final String WORK_PROFILE_SCREEN_LOCK_SETUP_MESSAGE =
+                    PREFIX + "WORK_PROFILE_SCREEN_LOCK_SETUP_MESSAGE";
+
+            /**
+             * Title for PreferenceScreen to launch picker for security method for the managed
+             * profile when there is none
+             */
+            public static final String WORK_PROFILE_SET_UNLOCK_LAUNCH_PICKER_TITLE =
+                    PREFIX + "WORK_PROFILE_SET_UNLOCK_LAUNCH_PICKER_TITLE";
+
+            /**
+             * Content of the dialog shown when the user only has one attempt left to provide the
+             * work lock pattern before the work profile is removed
+             */
+            public static final String WORK_PROFILE_LAST_PATTERN_ATTEMPT_BEFORE_WIPE =
+                    PREFIX + "WORK_PROFILE_LAST_PATTERN_ATTEMPT_BEFORE_WIPE";
+
+            /**
+             * Content of the dialog shown when the user only has one attempt left to provide the
+             * work lock pattern before the work profile is removed
+             */
+            public static final String WORK_PROFILE_LAST_PIN_ATTEMPT_BEFORE_WIPE =
+                    PREFIX + "WORK_PROFILE_LAST_PIN_ATTEMPT_BEFORE_WIPE";
+
+            /**
+             * Content of the dialog shown when the user only has one attempt left to provide the
+             * work lock pattern before the work profile is removed
+             */
+            public static final String WORK_PROFILE_LAST_PASSWORD_ATTEMPT_BEFORE_WIPE =
+                    PREFIX + "WORK_PROFILE_LAST_PASSWORD_ATTEMPT_BEFORE_WIPE";
+
+            /**
+             * Content of the dialog shown when the user has failed to provide the device lock too
+             * many times and the device is wiped
+             */
+            public static final String WORK_PROFILE_LOCK_ATTEMPTS_FAILED =
+                    PREFIX + "WORK_PROFILE_LOCK_ATTEMPTS_FAILED";
+
+            /**
+             * Content description for work profile accounts group
+             */
+            public static final String ACCESSIBILITY_CATEGORY_WORK =
+                    PREFIX + "ACCESSIBILITY_CATEGORY_WORK";
+
+            /**
+             * Content description for personal profile accounts group
+             */
+            public static final String ACCESSIBILITY_CATEGORY_PERSONAL =
+                    PREFIX + "ACCESSIBILITY_CATEGORY_PERSONAL";
+
+            /**
+             * Content description for work profile details page title
+             */
+            public static final String ACCESSIBILITY_WORK_ACCOUNT_TITLE =
+                    PREFIX + "ACCESSIBILITY_WORK_ACCOUNT_TITLE";
+
+            /**
+             * Content description for personal profile details page title
+             */
+            public static final String ACCESSIBILITY_PERSONAL_ACCOUNT_TITLE =
+                    PREFIX + "ACCESSIBILITY_PERSONAL_ACCOUNT_TITLE";
+
+            /**
+             * Title for work profile location switch
+             */
+            public static final String WORK_PROFILE_LOCATION_SWITCH_TITLE =
+                    PREFIX + "WORK_PROFILE_LOCATION_SWITCH_TITLE";
+
+            /**
+             * Header when setting work profile password
+             */
+            public static final String SET_WORK_PROFILE_PASSWORD_HEADER =
+                    PREFIX + "SET_WORK_PROFILE_PASSWORD_HEADER";
+
+            /**
+             * Header when setting work profile PIN
+             */
+            public static final String SET_WORK_PROFILE_PIN_HEADER =
+                    PREFIX + "SET_WORK_PROFILE_PIN_HEADER";
+
+            /**
+             * Header when setting work profile pattern
+             */
+            public static final String SET_WORK_PROFILE_PATTERN_HEADER =
+                    PREFIX + "SET_WORK_PROFILE_PATTERN_HEADER";
+
+            /**
+             * Header when confirming work profile password
+             */
+            public static final String CONFIRM_WORK_PROFILE_PASSWORD_HEADER =
+                    PREFIX + "CONFIRM_WORK_PROFILE_PASSWORD_HEADER";
+
+            /**
+             * Header when confirming work profile pin
+             */
+            public static final String CONFIRM_WORK_PROFILE_PIN_HEADER =
+                    PREFIX + "CONFIRM_WORK_PROFILE_PIN_HEADER";
+
+            /**
+             * Header when confirming work profile pattern
+             */
+            public static final String CONFIRM_WORK_PROFILE_PATTERN_HEADER =
+                    PREFIX + "CONFIRM_WORK_PROFILE_PATTERN_HEADER";
+
+            /**
+             * Header when re-entering work profile password
+             */
+            public static final String REENTER_WORK_PROFILE_PASSWORD_HEADER =
+                    PREFIX + "REENTER_WORK_PROFILE_PASSWORD_HEADER";
+
+            /**
+             * Header when re-entering work profile pin
+             */
+            public static final String REENTER_WORK_PROFILE_PIN_HEADER =
+                    PREFIX + "REENTER_WORK_PROFILE_PIN_HEADER";
+
+            /**
+             * Message to be used to explain the users that they need to enter their work pattern to
+             * continue a particular operation
+             */
+            public static final String WORK_PROFILE_CONFIRM_PATTERN =
+                    PREFIX + "WORK_PROFILE_CONFIRM_PATTERN";
+
+            /**
+             * Message to be used to explain the users that they need to enter their work pin to
+             * continue a particular operation
+             */
+            public static final String WORK_PROFILE_CONFIRM_PIN =
+                    PREFIX + "WORK_PROFILE_CONFIRM_PIN";
+
+            /**
+             * Message to be used to explain the users that they need to enter their work password
+             * to
+             * continue a particular operation
+             */
+            public static final String WORK_PROFILE_CONFIRM_PASSWORD =
+                    PREFIX + "WORK_PROFILE_CONFIRM_PASSWORD";
+
+            /**
+             * This string shows = PREFIX + "shows"; up on a screen where a user can enter a pattern
+             * that lets them access
+             * their work profile. This is an extra security measure that's required for them to
+             * continue
+             */
+            public static final String WORK_PROFILE_PATTERN_REQUIRED =
+                    PREFIX + "WORK_PROFILE_PATTERN_REQUIRED";
+
+            /**
+             * This string shows = PREFIX + "shows"; up on a screen where a user can enter a pin
+             * that lets them access
+             * their work profile. This is an extra security measure that's required for them to
+             * continue
+             */
+            public static final String WORK_PROFILE_PIN_REQUIRED =
+                    PREFIX + "WORK_PROFILE_PIN_REQUIRED";
+
+            /**
+             * This string shows = PREFIX + "shows"; up on a screen where a user can enter a
+             * password that lets them access
+             * their work profile. This is an extra security measure that's required for them to
+             * continue
+             */
+            public static final String WORK_PROFILE_PASSWORD_REQUIRED =
+                    PREFIX + "WORK_PROFILE_PASSWORD_REQUIRED";
+
+            /**
+             * Header for Work Profile security settings
+             */
+            public static final String WORK_PROFILE_SECURITY_TITLE =
+                    PREFIX + "WORK_PROFILE_SECURITY_TITLE";
+
+            /**
+             * Header for Work Profile unify locks settings
+             */
+            public static final String WORK_PROFILE_UNIFY_LOCKS_TITLE =
+                    PREFIX + "WORK_PROFILE_UNIFY_LOCKS_TITLE";
+
+            /**
+             * Setting option explanation to unify work and personal locks
+             */
+            public static final String WORK_PROFILE_UNIFY_LOCKS_SUMMARY =
+                    PREFIX + "WORK_PROFILE_UNIFY_LOCKS_SUMMARY";
+
+            /**
+             * Further explanation when the user wants to unify work and personal locks
+             */
+            public static final String WORK_PROFILE_UNIFY_LOCKS_DETAIL =
+                    PREFIX + "WORK_PROFILE_UNIFY_LOCKS_DETAIL";
+
+            /**
+             * Ask if the user wants to create a new lock for personal and work as the current work
+             * lock is not enough for the device
+             */
+            public static final String WORK_PROFILE_UNIFY_LOCKS_NONCOMPLIANT =
+                    PREFIX + "WORK_PROFILE_UNIFY_LOCKS_NONCOMPLIANT";
+
+            /**
+             * Title of 'Work profile keyboards & tools' preference category
+             */
+            public static final String WORK_PROFILE_KEYBOARDS_AND_TOOLS =
+                    PREFIX + "WORK_PROFILE_KEYBOARDS_AND_TOOLS";
+
+            /**
+             * Label for state when work profile is not available
+             */
+            public static final String WORK_PROFILE_NOT_AVAILABLE =
+                    PREFIX + "WORK_PROFILE_NOT_AVAILABLE";
+
+            /**
+             * Label for work profile setting (to allow turning work profile on and off)
+             */
+            public static final String WORK_PROFILE_SETTING = PREFIX + "WORK_PROFILE_SETTING";
+
+            /**
+             * Description of the work profile setting when the work profile is on
+             */
+            public static final String WORK_PROFILE_SETTING_ON_SUMMARY =
+                    PREFIX + "WORK_PROFILE_SETTING_ON_SUMMARY";
+
+            /**
+             * Description of the work profile setting when the work profile is off
+             */
+            public static final String WORK_PROFILE_SETTING_OFF_SUMMARY =
+                    PREFIX + "WORK_PROFILE_SETTING_OFF_SUMMARY";
+
+            /**
+             * Button text to remove work profile
+             */
+            public static final String REMOVE_WORK_PROFILE = PREFIX + "REMOVE_WORK_PROFILE";
+
+            /**
+             * Text of message to show to device owner user whose administrator has installed a SSL
+             * CA Cert
+             */
+            public static final String DEVICE_OWNER_INSTALLED_CERTIFICATE_AUTHORITY_WARNING =
+                    PREFIX + "DEVICE_OWNER_INSTALLED_CERTIFICATE_AUTHORITY_WARNING";
+
+            /**
+             * Text of message to show to work profile users whose administrator has installed a SSL
+             * CA Cert
+             */
+            public static final String WORK_PROFILE_INSTALLED_CERTIFICATE_AUTHORITY_WARNING =
+                    PREFIX + "WORK_PROFILE_INSTALLED_CERTIFICATE_AUTHORITY_WARNING";
+
+            /**
+             * Work profile removal confirmation title
+             */
+            public static final String WORK_PROFILE_CONFIRM_REMOVE_TITLE =
+                    PREFIX + "WORK_PROFILE_CONFIRM_REMOVE_TITLE";
+
+            /**
+             * Work profile removal confirmation message
+             */
+            public static final String WORK_PROFILE_CONFIRM_REMOVE_MESSAGE =
+                    PREFIX + "WORK_PROFILE_CONFIRM_REMOVE_MESSAGE";
+
+            /**
+             * Toast shown when an app in the work profile attempts to open notification settings
+             * and apps in the work profile cannot access notification settings
+             */
+            public static final String WORK_APPS_CANNOT_ACCESS_NOTIFICATION_SETTINGS =
+                    PREFIX + "WORK_APPS_CANNOT_ACCESS_NOTIFICATION_SETTINGS";
+
+            /**
+             * Work sound settings section header
+             */
+            public static final String WORK_PROFILE_SOUND_SETTINGS_SECTION_HEADER =
+                    PREFIX + "WORK_PROFILE_SOUND_SETTINGS_SECTION_HEADER";
+
+            /**
+             * Title for the switch that enables syncing of personal ringtones to work profile
+             */
+            public static final String WORK_PROFILE_USE_PERSONAL_SOUNDS_TITLE =
+                    PREFIX + "WORK_PROFILE_USE_PERSONAL_SOUNDS_TITLE";
+
+            /**
+             * Summary for the switch that enables syncing of personal ringtones to work profile
+             */
+            public static final String WORK_PROFILE_USE_PERSONAL_SOUNDS_SUMMARY =
+                    PREFIX + "WORK_PROFILE_USE_PERSONAL_SOUNDS_SUMMARY";
+
+            /**
+             * Title for the option defining the work profile phone ringtone
+             */
+            public static final String WORK_PROFILE_RINGTONE_TITLE =
+                    PREFIX + "WORK_PROFILE_RINGTONE_TITLE";
+
+            /**
+             * Title for the option defining the default work profile notification ringtone
+             */
+            public static final String WORK_PROFILE_NOTIFICATION_RINGTONE_TITLE =
+                    PREFIX + "WORK_PROFILE_NOTIFICATION_RINGTONE_TITLE";
+
+            /**
+             * Title for the option defining the default work alarm ringtone
+             */
+            public static final String WORK_PROFILE_ALARM_RINGTONE_TITLE =
+                    PREFIX + "WORK_PROFILE_ALARM_RINGTONE_TITLE";
+
+            /**
+             * Summary for sounds when sync with personal sounds is active
+             */
+            public static final String WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_ACTIVE_SUMMARY =
+                    PREFIX + "WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_ACTIVE_SUMMARY";
+
+            /**
+             * Title for dialog shown when enabling sync with personal sounds
+             */
+            public static final String
+                    ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_TITLE =
+                    PREFIX + "ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_TITLE";
+
+            /**
+             * Message for dialog shown when using the same sounds for work events as for personal
+             * events
+             */
+            public static final String
+                    ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_MESSAGE =
+                    PREFIX + "ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_MESSAGE";
+
+            /**
+             * Work profile notifications section header
+             */
+            public static final String WORK_PROFILE_NOTIFICATIONS_SECTION_HEADER =
+                    PREFIX + "WORK_PROFILE_NOTIFICATIONS_SECTION_HEADER";
+
+            /**
+             * Title for the option controlling notifications for work profile
+             */
+            public static final String WORK_PROFILE_LOCKED_NOTIFICATION_TITLE =
+                    PREFIX + "WORK_PROFILE_LOCKED_NOTIFICATION_TITLE";
+
+            /**
+             * Title for redacting sensitive content on lockscreen for work profiles
+             */
+            public static final String WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_TITLE =
+                    PREFIX + "WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_TITLE";
+
+            /**
+             * Summary for redacting sensitive content on lockscreen for work profiles
+             */
+            public static final String WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_SUMMARY =
+                    PREFIX + "WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_SUMMARY";
+
+            /**
+             * Indicates that the work profile admin doesn't allow this notification listener to
+             * access
+             * work profile notifications
+             */
+            public static final String WORK_PROFILE_NOTIFICATION_LISTENER_BLOCKED =
+                    PREFIX + "WORK_PROFILE_NOTIFICATION_LISTENER_BLOCKED";
+
+            /**
+             * This setting shows a user's connected work and personal apps.
+             */
+            public static final String CONNECTED_WORK_AND_PERSONAL_APPS_TITLE =
+                    PREFIX + "CONNECTED_WORK_AND_PERSONAL_APPS_TITLE";
+
+            /**
+             * This text lets a user know that if they connect work and personal apps,
+             * they will share permissions and can access each other's data
+             */
+            public static final String CONNECTED_APPS_SHARE_PERMISSIONS_AND_DATA =
+                    PREFIX + "CONNECTED_APPS_SHARE_PERMISSIONS_AND_DATA";
+
+            /**
+             * This text lets a user know that they should only connect work and personal apps if
+             * they
+             * trust the work app with their personal data
+             */
+            public static final String ONLY_CONNECT_TRUSTED_APPS =
+                    PREFIX + "ONLY_CONNECT_TRUSTED_APPS";
+
+            /**
+             * This text lets a user know how to disconnect work and personal apps
+             */
+            public static final String HOW_TO_DISCONNECT_APPS = PREFIX + "HOW_TO_DISCONNECT_APPS";
+
+            /**
+             * Title of confirmation dialog when connecting work and personal apps
+             */
+            public static final String CONNECT_APPS_DIALOG_TITLE =
+                    PREFIX + "CONNECT_APPS_DIALOG_TITLE";
+
+            /**
+             * This dialog is shown when a user tries to connect a work app to a personal
+             * app
+             */
+            public static final String CONNECT_APPS_DIALOG_SUMMARY =
+                    PREFIX + "CONNECT_APPS_DIALOG_SUMMARY";
+
+            /**
+             * This text lets the user know that their work app will be able to access data in their
+             * personal app
+             */
+            public static final String APP_CAN_ACCESS_PERSONAL_DATA =
+                    PREFIX + "APP_CAN_ACCESS_PERSONAL_DATA";
+
+            /**
+             * This text lets the user know that their work app will be able to use permissions in
+             * their personal app
+             */
+            public static final String APP_CAN_ACCESS_PERSONAL_PERMISSIONS =
+                    PREFIX + "APP_CAN_ACCESS_PERSONAL_PERMISSIONS";
+
+            /**
+             * lets a user know that they need to install an app in their work profile in order to
+             * connect it to the corresponding personal app
+             */
+            public static final String INSTALL_IN_WORK_PROFILE_TO_CONNECT_PROMPT =
+                    PREFIX + "INSTALL_IN_WORK_PROFILE_TO_CONNECT_PROMPT";
+
+            /**
+             * lets a user know that they need to install an app in their personal profile in order
+             * to
+             * connect it to the corresponding work app
+             */
+            public static final String INSTALL_IN_PERSONAL_PROFILE_TO_CONNECT_PROMPT =
+                    PREFIX + "INSTALL_IN_PERSONAL_PROFILE_TO_CONNECT_PROMPT";
+
+            /**
+             * Header for showing the organisation managing the work profile
+             */
+            public static final String WORK_PROFILE_MANAGED_BY = PREFIX + "WORK_PROFILE_MANAGED_BY";
+
+            /**
+             * Summary showing the enterprise who manages the device or profile.
+             */
+            public static final String MANAGED_BY = PREFIX + "MANAGED_BY";
+
+            /**
+             * Warning message about disabling usage access on profile owner
+             */
+            public static final String WORK_PROFILE_DISABLE_USAGE_ACCESS_WARNING =
+                    PREFIX + "WORK_PROFILE_DISABLE_USAGE_ACCESS_WARNING";
+
+            /**
+             * Title for dialog displayed when user taps a setting on their phone that's blocked by
+             * their IT admin
+             */
+            public static final String DISABLED_BY_IT_ADMIN_TITLE =
+                    PREFIX + "DISABLED_BY_IT_ADMIN_TITLE";
+
+            /**
+             * Shown when the user tries to change phone settings that are blocked by their IT admin
+             */
+            public static final String CONTACT_YOUR_IT_ADMIN = PREFIX + "CONTACT_YOUR_IT_ADMIN";
+
+            /**
+             * warn user about policies the admin can set in a work profile
+             */
+            public static final String WORK_PROFILE_ADMIN_POLICIES_WARNING =
+                    PREFIX + "WORK_PROFILE_ADMIN_POLICIES_WARNING";
+
+            /**
+             * warn user about policies the admin can set on a user
+             */
+            public static final String USER_ADMIN_POLICIES_WARNING =
+                    PREFIX + "USER_ADMIN_POLICIES_WARNING";
+
+            /**
+             * warn user about policies the admin can set on a device
+             */
+            public static final String DEVICE_ADMIN_POLICIES_WARNING =
+                    PREFIX + "DEVICE_ADMIN_POLICIES_WARNING";
+
+            /**
+             * Condition that work profile is off
+             */
+            public static final String WORK_PROFILE_OFF_CONDITION_TITLE =
+                    PREFIX + "WORK_PROFILE_OFF_CONDITION_TITLE";
+
+            /**
+             * Title of work profile setting page
+             */
+            public static final String MANAGED_PROFILE_SETTINGS_TITLE =
+                    PREFIX + "MANAGED_PROFILE_SETTINGS_TITLE";
+
+            /**
+             * Setting that lets a user's personal apps identify contacts using the user's work
+             * directory
+             */
+            public static final String WORK_PROFILE_CONTACT_SEARCH_TITLE =
+                    PREFIX + "WORK_PROFILE_CONTACT_SEARCH_TITLE";
+
+            /**
+             * This setting lets a user's personal apps identify contacts using the user's work
+             * directory
+             */
+            public static final String WORK_PROFILE_CONTACT_SEARCH_SUMMARY =
+                    PREFIX + "WORK_PROFILE_CONTACT_SEARCH_SUMMARY";
+
+            /**
+             * This setting lets the user show their work events on their personal calendar
+             */
+            public static final String CROSS_PROFILE_CALENDAR_TITLE =
+                    PREFIX + "CROSS_PROFILE_CALENDAR_TITLE";
+
+            /**
+             * Setting description. If the user turns on this setting, they can see their work
+             * events on their personal calendar
+             */
+            public static final String CROSS_PROFILE_CALENDAR_SUMMARY =
+                    PREFIX + "CROSS_PROFILE_CALENDAR_SUMMARY";
+
+            /**
+             * Label explaining that an always-on VPN was set by the admin in the personal profile
+             */
+            public static final String ALWAYS_ON_VPN_PERSONAL_PROFILE =
+                    PREFIX + "ALWAYS_ON_VPN_PERSONAL_PROFILE";
+
+            /**
+             * Label explaining that an always-on VPN was set by the admin for the entire device
+             */
+            public static final String ALWAYS_ON_VPN_DEVICE = PREFIX + "ALWAYS_ON_VPN_DEVICE";
+
+            /**
+             * Label explaining that an always-on VPN was set by the admin in the work profile
+             */
+            public static final String ALWAYS_ON_VPN_WORK_PROFILE =
+                    PREFIX + "ALWAYS_ON_VPN_WORK_PROFILE";
+
+            /**
+             * Label explaining that the admin installed trusted CA certificates in personal profile
+             */
+            public static final String CA_CERTS_PERSONAL_PROFILE =
+                    PREFIX + "CA_CERTS_PERSONAL_PROFILE";
+
+            /**
+             * Label explaining that the admin installed trusted CA certificates in work profile
+             */
+            public static final String CA_CERTS_WORK_PROFILE = PREFIX + "CA_CERTS_WORK_PROFILE";
+
+            /**
+             * Label explaining that the admin installed trusted CA certificates for the entire
+             * device
+             */
+            public static final String CA_CERTS_DEVICE = PREFIX + "CA_CERTS_DEVICE";
+
+            /**
+             * Label explaining that the admin can lock the device and change the user's password
+             */
+            public static final String ADMIN_CAN_LOCK_DEVICE = PREFIX + "ADMIN_CAN_LOCK_DEVICE";
+
+            /**
+             * Label explaining that the admin can wipe the device remotely
+             */
+            public static final String ADMIN_CAN_WIPE_DEVICE = PREFIX + "ADMIN_CAN_WIPE_DEVICE";
+
+            /**
+             * Label explaining that the admin configured the device to wipe itself when the
+             * password is mistyped too many times
+             */
+            public static final String ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_DEVICE =
+                    PREFIX + "ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_DEVICE";
+
+            /**
+             * Label explaining that the admin configured the work profile to wipe itself when the
+             * password is mistyped too many times
+             */
+            public static final String ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_WORK_PROFILE =
+                    PREFIX + "ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_WORK_PROFILE";
+
+            /**
+             * Message indicating that the device is enterprise-managed by a Device Owner
+             */
+            public static final String DEVICE_MANAGED_WITHOUT_NAME =
+                    PREFIX + "DEVICE_MANAGED_WITHOUT_NAME";
+
+            /**
+             * Message indicating that the device is enterprise-managed by a Device Owner
+             */
+            public static final String DEVICE_MANAGED_WITH_NAME =
+                    PREFIX + "DEVICE_MANAGED_WITH_NAME";
+
+            /**
+             * Subtext of work profile app for current setting
+             */
+            public static final String WORK_PROFILE_APP_SUBTEXT =
+                    PREFIX + "WORK_PROFILE_APP_SUBTEXT";
+
+            /**
+             * Subtext of personal profile app for current setting
+             */
+            public static final String PERSONAL_PROFILE_APP_SUBTEXT =
+                    PREFIX + "PERSONAL_PROFILE_APP_SUBTEXT";
+
+            /**
+             * Title shown for work menu item that launches fingerprint settings or enrollment
+             */
+            public static final String FINGERPRINT_FOR_WORK = PREFIX + "FINGERPRINT_FOR_WORK";
+
+            /**
+             * Message shown in face enrollment dialog, when face unlock is disabled by device admin
+             */
+            public static final String FACE_UNLOCK_DISABLED = PREFIX + "FACE_UNLOCK_DISABLED";
+
+            /**
+             * message shown in fingerprint enrollment dialog, when fingerprint unlock is disabled
+             * by device admin
+             */
+            public static final String FINGERPRINT_UNLOCK_DISABLED =
+                    PREFIX + "FINGERPRINT_UNLOCK_DISABLED";
+
+            /**
+             * Text shown in fingerprint settings explaining what the fingerprint can be used for in
+             * the case unlocking is disabled
+             */
+            public static final String FINGERPRINT_UNLOCK_DISABLED_EXPLANATION =
+                    PREFIX + "FINGERPRINT_UNLOCK_DISABLED_EXPLANATION";
+
+            /**
+             * Error shown when in PIN mode and PIN has been used recently
+             */
+            public static final String PIN_RECENTLY_USED = PREFIX + "PIN_RECENTLY_USED";
+
+            /**
+             * Error shown when in PASSWORD mode and password has been used recently
+             */
+            public static final String PASSWORD_RECENTLY_USED = PREFIX + "PASSWORD_RECENTLY_USED";
+
+            /**
+             * Title of preference to manage device admin apps
+             */
+            public static final String MANAGE_DEVICE_ADMIN_APPS =
+                    PREFIX + "MANAGE_DEVICE_ADMIN_APPS";
+
+            /**
+             * Inform the user that currently no device admin apps are installed and active
+             */
+            public static final String NUMBER_OF_DEVICE_ADMINS_NONE =
+                    PREFIX + "NUMBER_OF_DEVICE_ADMINS_NONE";
+
+            /**
+             * Inform the user how many device admin apps are installed and active
+             */
+            public static final String NUMBER_OF_DEVICE_ADMINS = PREFIX + "NUMBER_OF_DEVICE_ADMINS";
+
+            /**
+             * Title that asks the user to contact the IT admin to reset password
+             */
+            public static final String FORGOT_PASSWORD_TITLE = PREFIX + "FORGOT_PASSWORD_TITLE";
+
+            /**
+             * Content that asks the user to contact the IT admin to reset password
+             */
+            public static final String FORGOT_PASSWORD_TEXT = PREFIX + "FORGOT_PASSWORD_TEXT";
+
+            /**
+             * Error message shown when trying to move device administrators to external disks, such
+             * as SD card
+             */
+            public static final String ERROR_MOVE_DEVICE_ADMIN = PREFIX + "ERROR_MOVE_DEVICE_ADMIN";
+
+            /**
+             * Device admin app settings title
+             */
+            public static final String DEVICE_ADMIN_SETTINGS_TITLE =
+                    PREFIX + "DEVICE_ADMIN_SETTINGS_TITLE";
+
+            /**
+             * Button to remove the active device admin app
+             */
+            public static final String REMOVE_DEVICE_ADMIN = PREFIX + "REMOVE_DEVICE_ADMIN";
+
+            /**
+             * Button to uninstall the device admin app
+             */
+            public static final String UNINSTALL_DEVICE_ADMIN = PREFIX + "UNINSTALL_DEVICE_ADMIN";
+
+            /**
+             * Button to deactivate and uninstall the device admin app
+             */
+            public static final String REMOVE_AND_UNINSTALL_DEVICE_ADMIN =
+                    PREFIX + "REMOVE_AND_UNINSTALL_DEVICE_ADMIN";
+
+            /**
+             * Title for selecting device admin apps
+             */
+            public static final String SELECT_DEVICE_ADMIN_APPS =
+                    PREFIX + "SELECT_DEVICE_ADMIN_APPS";
+
+            /**
+             * Message when there are no available device admin apps to display
+             */
+            public static final String NO_DEVICE_ADMINS = PREFIX + "NO_DEVICE_ADMINS";
+
+            /**
+             * Title for screen to add a device admin app
+             */
+            public static final String ACTIVATE_DEVICE_ADMIN_APP =
+                    PREFIX + "ACTIVATE_DEVICE_ADMIN_APP";
+
+            /**
+             * Label for button to set the active device admin
+             */
+            public static final String ACTIVATE_THIS_DEVICE_ADMIN_APP =
+                    PREFIX + "ACTIVATE_THIS_DEVICE_ADMIN_APP";
+
+            /**
+             * Activate a specific device admin app title
+             */
+            public static final String ACTIVATE_DEVICE_ADMIN_APP_TITLE =
+                    PREFIX + "ACTIVATE_DEVICE_ADMIN_APP_TITLE";
+
+            /**
+             * Device admin warning message about policies a not active admin can use
+             */
+            public static final String NEW_DEVICE_ADMIN_WARNING =
+                    PREFIX + "NEW_DEVICE_ADMIN_WARNING";
+
+            /**
+             * Simplified device admin warning message
+             */
+            public static final String NEW_DEVICE_ADMIN_WARNING_SIMPLIFIED =
+                    PREFIX + "NEW_DEVICE_ADMIN_WARNING_SIMPLIFIED";
+
+            /**
+             * Device admin warning message about policies the active admin can use
+             */
+            public static final String ACTIVE_DEVICE_ADMIN_WARNING =
+                    PREFIX + "ACTIVE_DEVICE_ADMIN_WARNING";
+
+            /**
+             * Title for screen to set a profile owner
+             */
+            public static final String SET_PROFILE_OWNER_TITLE = PREFIX + "SET_PROFILE_OWNER_TITLE";
+
+            /**
+             * Simplified title for dialog to set a profile owner
+             */
+            public static final String SET_PROFILE_OWNER_DIALOG_TITLE =
+                    PREFIX + "SET_PROFILE_OWNER_DIALOG_TITLE";
+
+            /**
+             * Warning when trying to add a profile owner admin after setup has completed
+             */
+            public static final String SET_PROFILE_OWNER_POSTSETUP_WARNING =
+                    PREFIX + "SET_PROFILE_OWNER_POSTSETUP_WARNING";
+
+            /**
+             * Message displayed to let the user know that some of the options are disabled by admin
+             */
+            public static final String OTHER_OPTIONS_DISABLED_BY_ADMIN =
+                    PREFIX + "OTHER_OPTIONS_DISABLED_BY_ADMIN";
+
+            /**
+             * This is shown if the authenticator for a given account fails to remove it due to
+             * admin restrictions
+             */
+            public static final String REMOVE_ACCOUNT_FAILED_ADMIN_RESTRICTION =
+                    PREFIX + "REMOVE_ACCOUNT_FAILED_ADMIN_RESTRICTION";
+
+            /**
+             * Url for learning more about IT admin policy disabling
+             */
+            public static final String IT_ADMIN_POLICY_DISABLING_INFO_URL =
+                    PREFIX + "IT_ADMIN_POLICY_DISABLING_INFO_URL";
+
+            /**
+             * Title of dialog shown to ask for user consent for sharing a bugreport that was
+             * requested
+             * remotely by the IT administrator
+             */
+            public static final String SHARE_REMOTE_BUGREPORT_DIALOG_TITLE =
+                    PREFIX + "SHARE_REMOTE_BUGREPORT_DIALOG_TITLE";
+
+            /**
+             * Message of a dialog shown to ask for user consent for sharing a bugreport that was
+             * requested remotely by the IT administrator
+             */
+            public static final String SHARE_REMOTE_BUGREPORT_FINISHED_REQUEST_CONSENT =
+                    PREFIX + "SHARE_REMOTE_BUGREPORT_FINISHED_REQUEST_CONSENT";
+
+            /**
+             * Message of a dialog shown to ask for user consent for sharing a bugreport that was
+             * requested remotely by the IT administrator and it's still being taken
+             */
+            public static final String SHARE_REMOTE_BUGREPORT_NOT_FINISHED_REQUEST_CONSENT =
+                    PREFIX + "SHARE_REMOTE_BUGREPORT_NOT_FINISHED_REQUEST_CONSENT";
+
+            /**
+             * Message of a dialog shown to inform that the remote bugreport that was requested
+             * remotely by the IT administrator is still being taken and will be shared when
+             * finished
+             */
+            public static final String SHARING_REMOTE_BUGREPORT_MESSAGE =
+                    PREFIX + "SHARING_REMOTE_BUGREPORT_MESSAGE";
+
+            /**
+             * Managed device information screen title
+             */
+            public static final String MANAGED_DEVICE_INFO = PREFIX + "MANAGED_DEVICE_INFO";
+
+            /**
+             * Summary for managed device info section
+             */
+            public static final String MANAGED_DEVICE_INFO_SUMMARY =
+                    PREFIX + "MANAGED_DEVICE_INFO_SUMMARY";
+
+            /**
+             * Summary for managed device info section including organization name
+             */
+            public static final String MANAGED_DEVICE_INFO_SUMMARY_WITH_NAME =
+                    PREFIX + "MANAGED_DEVICE_INFO_SUMMARY_WITH_NAME";
+
+            /**
+             * Enterprise Privacy settings header, summarizing the powers that the admin has
+             */
+            public static final String ENTERPRISE_PRIVACY_HEADER =
+                    PREFIX + "ENTERPRISE_PRIVACY_HEADER";
+
+            /**
+             * Types of information your organization can see section title
+             */
+            public static final String INFORMATION_YOUR_ORGANIZATION_CAN_SEE_TITLE =
+                    PREFIX + "INFORMATION_YOUR_ORGANIZATION_CAN_SEE_TITLE";
+
+            /**
+             * Changes made by your organization's admin section title
+             */
+            public static final String CHANGES_MADE_BY_YOUR_ORGANIZATION_ADMIN_TITLE =
+                    PREFIX + "CHANGES_MADE_BY_YOUR_ORGANIZATION_ADMIN_TITLE";
+
+            /**
+             * Your access to this device section title
+             */
+            public static final String YOUR_ACCESS_TO_THIS_DEVICE_TITLE =
+                    PREFIX + "YOUR_ACCESS_TO_THIS_DEVICE_TITLE";
+
+            /**
+             * Things the admin can see: data associated with the work account
+             */
+            public static final String ADMIN_CAN_SEE_WORK_DATA_WARNING =
+                    PREFIX + "ADMIN_CAN_SEE_WORK_DATA_WARNING";
+
+            /**
+             * Things the admin can see: Apps installed on the device
+             */
+            public static final String ADMIN_CAN_SEE_APPS_WARNING =
+                    PREFIX + "ADMIN_CAN_SEE_APPS_WARNING";
+
+            /**
+             * Things the admin can see: Amount of time and data spent in each app
+             */
+            public static final String ADMIN_CAN_SEE_USAGE_WARNING =
+                    PREFIX + "ADMIN_CAN_SEE_USAGE_WARNING";
+
+            /**
+             * Things the admin can see: Most recent network traffic log
+             */
+            public static final String ADMIN_CAN_SEE_NETWORK_LOGS_WARNING =
+                    PREFIX + "ADMIN_CAN_SEE_NETWORK_LOGS_WARNING";
+            /**
+             * Things the admin can see: Most recent bug report
+             */
+            public static final String ADMIN_CAN_SEE_BUG_REPORT_WARNING =
+                    PREFIX + "ADMIN_CAN_SEE_BUG_REPORT_WARNING";
+
+            /**
+             * Things the admin can see: Security logs
+             */
+            public static final String ADMIN_CAN_SEE_SECURITY_LOGS_WARNING =
+                    PREFIX + "ADMIN_CAN_SEE_SECURITY_LOGS_WARNING";
+
+            /**
+             * Indicate that the admin never took a given action so far (e.g. did not retrieve
+             * security logs or request bug reports).
+             */
+            public static final String ADMIN_ACTION_NONE = PREFIX + "ADMIN_ACTION_NONE";
+
+            /**
+             * Indicate that the admin installed one or more apps on the device
+             */
+            public static final String ADMIN_ACTION_APPS_INSTALLED =
+                    PREFIX + "ADMIN_ACTION_APPS_INSTALLED";
+
+            /**
+             * Explaining that the number of apps is an estimation
+             */
+            public static final String ADMIN_ACTION_APPS_COUNT_ESTIMATED =
+                    PREFIX + "ADMIN_ACTION_APPS_COUNT_ESTIMATED";
+
+            /**
+             * Indicating the minimum number of apps that a label refers to
+             */
+            public static final String ADMIN_ACTIONS_APPS_COUNT_MINIMUM =
+                    PREFIX + "ADMIN_ACTIONS_APPS_COUNT_MINIMUM";
+
+            /**
+             * Indicate that the admin granted one or more apps access to the device's location
+             */
+            public static final String ADMIN_ACTION_ACCESS_LOCATION =
+                    PREFIX + "ADMIN_ACTION_ACCESS_LOCATION";
+
+            /**
+             * Indicate that the admin granted one or more apps access to the microphone
+             */
+            public static final String ADMIN_ACTION_ACCESS_MICROPHONE =
+                    PREFIX + "ADMIN_ACTION_ACCESS_MICROPHONE";
+
+            /**
+             * Indicate that the admin granted one or more apps access to the camera
+             */
+            public static final String ADMIN_ACTION_ACCESS_CAMERA =
+                    PREFIX + "ADMIN_ACTION_ACCESS_CAMERA";
+
+            /**
+             * Indicate that the admin set one or more apps as defaults for common actions
+             */
+            public static final String ADMIN_ACTION_SET_DEFAULT_APPS =
+                    PREFIX + "ADMIN_ACTION_SET_DEFAULT_APPS";
+
+            /**
+             * Indicate the number of apps that a label refers to
+             */
+            public static final String ADMIN_ACTIONS_APPS_COUNT =
+                    PREFIX + "ADMIN_ACTIONS_APPS_COUNT";
+
+            /**
+             * Indicate that the current input method was set by the admin
+             */
+            public static final String ADMIN_ACTION_SET_CURRENT_INPUT_METHOD =
+                    PREFIX + "ADMIN_ACTION_SET_CURRENT_INPUT_METHOD";
+
+            /**
+             * The input method set by the admin
+             */
+            public static final String ADMIN_ACTION_SET_INPUT_METHOD_NAME =
+                    PREFIX + "ADMIN_ACTION_SET_INPUT_METHOD_NAME";
+
+            /**
+             * Indicate that a global HTTP proxy was set by the admin
+             */
+            public static final String ADMIN_ACTION_SET_HTTP_PROXY =
+                    PREFIX + "ADMIN_ACTION_SET_HTTP_PROXY";
+
+            /**
+             * Summary for Enterprise Privacy settings, explaining what the user can expect to find
+             * under it
+             */
+            public static final String WORK_PROFILE_PRIVACY_POLICY_INFO_SUMMARY =
+                    PREFIX + "WORK_PROFILE_PRIVACY_POLICY_INFO_SUMMARY";
+
+            /**
+             * Setting on privacy settings screen that will show work policy info
+             */
+            public static final String WORK_PROFILE_PRIVACY_POLICY_INFO =
+                    PREFIX + "WORK_PROFILE_PRIVACY_POLICY_INFO";
+
+            /**
+             * Search keywords for connected work and personal apps
+             */
+            public static final String CONNECTED_APPS_SEARCH_KEYWORDS =
+                    PREFIX + "CONNECTED_APPS_SEARCH_KEYWORDS";
+
+            /**
+             * Work profile unification keywords
+             */
+            public static final String WORK_PROFILE_UNIFICATION_SEARCH_KEYWORDS =
+                    PREFIX + "WORK_PROFILE_UNIFICATION_SEARCH_KEYWORDS";
+
+            /**
+             * Accounts keywords
+             */
+            public static final String ACCOUNTS_SEARCH_KEYWORDS =
+                    PREFIX + "ACCOUNTS_SEARCH_KEYWORDS";
+
+            /**
+             * Summary for settings preference disabled by administrator
+             */
+            public static final String CONTROLLED_BY_ADMIN_SUMMARY =
+                    PREFIX + "CONTROLLED_BY_ADMIN_SUMMARY";
+
+            /**
+             * User label for a work profile
+             */
+            public static final String WORK_PROFILE_USER_LABEL = PREFIX + "WORK_PROFILE_USER_LABEL";
+
+            /**
+             * Header for items under the work user
+             */
+            public static final String WORK_CATEGORY_HEADER = PREFIX + "WORK_CATEGORY_HEADER";
+
+            /**
+             * Header for items under the personal user
+             */
+            public static final String PERSONAL_CATEGORY_HEADER = PREFIX + "category_personal";
+
+            /**
+             * @hide
+             */
+            static Set<String> buildStringsSet() {
+                Set<String> strings = new HashSet<>();
+                strings.add(FACE_SETTINGS_FOR_WORK_TITLE);
+                strings.add(WORK_PROFILE_FINGERPRINT_LAST_DELETE_MESSAGE);
+                strings.add(WORK_PROFILE_IT_ADMIN_CANT_RESET_SCREEN_LOCK);
+                strings.add(WORK_PROFILE_SCREEN_LOCK_SETUP_MESSAGE);
+                strings.add(WORK_PROFILE_SET_UNLOCK_LAUNCH_PICKER_TITLE);
+                strings.add(WORK_PROFILE_LAST_PATTERN_ATTEMPT_BEFORE_WIPE);
+                strings.add(WORK_PROFILE_LAST_PIN_ATTEMPT_BEFORE_WIPE);
+                strings.add(WORK_PROFILE_LAST_PASSWORD_ATTEMPT_BEFORE_WIPE);
+                strings.add(WORK_PROFILE_LOCK_ATTEMPTS_FAILED);
+                strings.add(ACCESSIBILITY_CATEGORY_WORK);
+                strings.add(ACCESSIBILITY_CATEGORY_PERSONAL);
+                strings.add(ACCESSIBILITY_WORK_ACCOUNT_TITLE);
+                strings.add(ACCESSIBILITY_PERSONAL_ACCOUNT_TITLE);
+                strings.add(WORK_PROFILE_LOCATION_SWITCH_TITLE);
+                strings.add(SET_WORK_PROFILE_PASSWORD_HEADER);
+                strings.add(SET_WORK_PROFILE_PIN_HEADER);
+                strings.add(SET_WORK_PROFILE_PATTERN_HEADER);
+                strings.add(CONFIRM_WORK_PROFILE_PASSWORD_HEADER);
+                strings.add(CONFIRM_WORK_PROFILE_PIN_HEADER);
+                strings.add(CONFIRM_WORK_PROFILE_PATTERN_HEADER);
+                strings.add(REENTER_WORK_PROFILE_PASSWORD_HEADER);
+                strings.add(REENTER_WORK_PROFILE_PIN_HEADER);
+                strings.add(WORK_PROFILE_CONFIRM_PATTERN);
+                strings.add(WORK_PROFILE_CONFIRM_PIN);
+                strings.add(WORK_PROFILE_PASSWORD_REQUIRED);
+                strings.add(WORK_PROFILE_SECURITY_TITLE);
+                strings.add(WORK_PROFILE_UNIFY_LOCKS_TITLE);
+                strings.add(WORK_PROFILE_UNIFY_LOCKS_SUMMARY);
+                strings.add(WORK_PROFILE_UNIFY_LOCKS_DETAIL);
+                strings.add(WORK_PROFILE_UNIFY_LOCKS_NONCOMPLIANT);
+                strings.add(WORK_PROFILE_KEYBOARDS_AND_TOOLS);
+                strings.add(WORK_PROFILE_NOT_AVAILABLE);
+                strings.add(WORK_PROFILE_SETTING);
+                strings.add(WORK_PROFILE_SETTING_ON_SUMMARY);
+                strings.add(WORK_PROFILE_SETTING_OFF_SUMMARY);
+                strings.add(REMOVE_WORK_PROFILE);
+                strings.add(DEVICE_OWNER_INSTALLED_CERTIFICATE_AUTHORITY_WARNING);
+                strings.add(WORK_PROFILE_INSTALLED_CERTIFICATE_AUTHORITY_WARNING);
+                strings.add(WORK_PROFILE_CONFIRM_REMOVE_TITLE);
+                strings.add(WORK_PROFILE_CONFIRM_REMOVE_MESSAGE);
+                strings.add(WORK_APPS_CANNOT_ACCESS_NOTIFICATION_SETTINGS);
+                strings.add(WORK_PROFILE_SOUND_SETTINGS_SECTION_HEADER);
+                strings.add(WORK_PROFILE_USE_PERSONAL_SOUNDS_TITLE);
+                strings.add(WORK_PROFILE_USE_PERSONAL_SOUNDS_SUMMARY);
+                strings.add(WORK_PROFILE_RINGTONE_TITLE);
+                strings.add(WORK_PROFILE_NOTIFICATION_RINGTONE_TITLE);
+                strings.add(WORK_PROFILE_ALARM_RINGTONE_TITLE);
+                strings.add(WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_ACTIVE_SUMMARY);
+                strings.add(ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_TITLE);
+                strings.add(ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_MESSAGE);
+                strings.add(WORK_PROFILE_NOTIFICATIONS_SECTION_HEADER);
+                strings.add(WORK_PROFILE_LOCKED_NOTIFICATION_TITLE);
+                strings.add(WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_TITLE);
+                strings.add(WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_SUMMARY);
+                strings.add(WORK_PROFILE_NOTIFICATION_LISTENER_BLOCKED);
+                strings.add(CONNECTED_WORK_AND_PERSONAL_APPS_TITLE);
+                strings.add(CONNECTED_APPS_SHARE_PERMISSIONS_AND_DATA);
+                strings.add(ONLY_CONNECT_TRUSTED_APPS);
+                strings.add(HOW_TO_DISCONNECT_APPS);
+                strings.add(CONNECT_APPS_DIALOG_TITLE);
+                strings.add(CONNECT_APPS_DIALOG_SUMMARY);
+                strings.add(APP_CAN_ACCESS_PERSONAL_DATA);
+                strings.add(APP_CAN_ACCESS_PERSONAL_PERMISSIONS);
+                strings.add(INSTALL_IN_WORK_PROFILE_TO_CONNECT_PROMPT);
+                strings.add(INSTALL_IN_PERSONAL_PROFILE_TO_CONNECT_PROMPT);
+                strings.add(WORK_PROFILE_MANAGED_BY);
+                strings.add(MANAGED_BY);
+                strings.add(WORK_PROFILE_DISABLE_USAGE_ACCESS_WARNING);
+                strings.add(DISABLED_BY_IT_ADMIN_TITLE);
+                strings.add(CONTACT_YOUR_IT_ADMIN);
+                strings.add(WORK_PROFILE_ADMIN_POLICIES_WARNING);
+                strings.add(USER_ADMIN_POLICIES_WARNING);
+                strings.add(DEVICE_ADMIN_POLICIES_WARNING);
+                strings.add(WORK_PROFILE_OFF_CONDITION_TITLE);
+                strings.add(MANAGED_PROFILE_SETTINGS_TITLE);
+                strings.add(WORK_PROFILE_CONTACT_SEARCH_TITLE);
+                strings.add(WORK_PROFILE_CONTACT_SEARCH_SUMMARY);
+                strings.add(CROSS_PROFILE_CALENDAR_TITLE);
+                strings.add(CROSS_PROFILE_CALENDAR_SUMMARY);
+                strings.add(ALWAYS_ON_VPN_PERSONAL_PROFILE);
+                strings.add(ALWAYS_ON_VPN_DEVICE);
+                strings.add(ALWAYS_ON_VPN_WORK_PROFILE);
+                strings.add(CA_CERTS_PERSONAL_PROFILE);
+                strings.add(CA_CERTS_WORK_PROFILE);
+                strings.add(CA_CERTS_DEVICE);
+                strings.add(ADMIN_CAN_LOCK_DEVICE);
+                strings.add(ADMIN_CAN_WIPE_DEVICE);
+                strings.add(ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_DEVICE);
+                strings.add(ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_WORK_PROFILE);
+                strings.add(DEVICE_MANAGED_WITHOUT_NAME);
+                strings.add(DEVICE_MANAGED_WITH_NAME);
+                strings.add(WORK_PROFILE_APP_SUBTEXT);
+                strings.add(PERSONAL_PROFILE_APP_SUBTEXT);
+                strings.add(FINGERPRINT_FOR_WORK);
+                strings.add(FACE_UNLOCK_DISABLED);
+                strings.add(FINGERPRINT_UNLOCK_DISABLED);
+                strings.add(FINGERPRINT_UNLOCK_DISABLED_EXPLANATION);
+                strings.add(PIN_RECENTLY_USED);
+                strings.add(PASSWORD_RECENTLY_USED);
+                strings.add(MANAGE_DEVICE_ADMIN_APPS);
+                strings.add(NUMBER_OF_DEVICE_ADMINS_NONE);
+                strings.add(NUMBER_OF_DEVICE_ADMINS);
+                strings.add(FORGOT_PASSWORD_TITLE);
+                strings.add(FORGOT_PASSWORD_TEXT);
+                strings.add(ERROR_MOVE_DEVICE_ADMIN);
+                strings.add(DEVICE_ADMIN_SETTINGS_TITLE);
+                strings.add(REMOVE_DEVICE_ADMIN);
+                strings.add(UNINSTALL_DEVICE_ADMIN);
+                strings.add(REMOVE_AND_UNINSTALL_DEVICE_ADMIN);
+                strings.add(SELECT_DEVICE_ADMIN_APPS);
+                strings.add(NO_DEVICE_ADMINS);
+                strings.add(ACTIVATE_DEVICE_ADMIN_APP);
+                strings.add(ACTIVATE_THIS_DEVICE_ADMIN_APP);
+                strings.add(ACTIVATE_DEVICE_ADMIN_APP_TITLE);
+                strings.add(NEW_DEVICE_ADMIN_WARNING);
+                strings.add(NEW_DEVICE_ADMIN_WARNING_SIMPLIFIED);
+                strings.add(ACTIVE_DEVICE_ADMIN_WARNING);
+                strings.add(SET_PROFILE_OWNER_TITLE);
+                strings.add(SET_PROFILE_OWNER_DIALOG_TITLE);
+                strings.add(SET_PROFILE_OWNER_POSTSETUP_WARNING);
+                strings.add(OTHER_OPTIONS_DISABLED_BY_ADMIN);
+                strings.add(REMOVE_ACCOUNT_FAILED_ADMIN_RESTRICTION);
+                strings.add(IT_ADMIN_POLICY_DISABLING_INFO_URL);
+                strings.add(SHARE_REMOTE_BUGREPORT_DIALOG_TITLE);
+                strings.add(SHARE_REMOTE_BUGREPORT_FINISHED_REQUEST_CONSENT);
+                strings.add(SHARE_REMOTE_BUGREPORT_NOT_FINISHED_REQUEST_CONSENT);
+                strings.add(SHARING_REMOTE_BUGREPORT_MESSAGE);
+                strings.add(MANAGED_DEVICE_INFO);
+                strings.add(MANAGED_DEVICE_INFO_SUMMARY);
+                strings.add(MANAGED_DEVICE_INFO_SUMMARY_WITH_NAME);
+                strings.add(ENTERPRISE_PRIVACY_HEADER);
+                strings.add(INFORMATION_YOUR_ORGANIZATION_CAN_SEE_TITLE);
+                strings.add(CHANGES_MADE_BY_YOUR_ORGANIZATION_ADMIN_TITLE);
+                strings.add(YOUR_ACCESS_TO_THIS_DEVICE_TITLE);
+                strings.add(ADMIN_CAN_SEE_WORK_DATA_WARNING);
+                strings.add(ADMIN_CAN_SEE_APPS_WARNING);
+                strings.add(ADMIN_CAN_SEE_USAGE_WARNING);
+                strings.add(ADMIN_CAN_SEE_NETWORK_LOGS_WARNING);
+                strings.add(ADMIN_CAN_SEE_BUG_REPORT_WARNING);
+                strings.add(ADMIN_CAN_SEE_SECURITY_LOGS_WARNING);
+                strings.add(ADMIN_ACTION_NONE);
+                strings.add(ADMIN_ACTION_APPS_INSTALLED);
+                strings.add(ADMIN_ACTION_APPS_COUNT_ESTIMATED);
+                strings.add(ADMIN_ACTIONS_APPS_COUNT_MINIMUM);
+                strings.add(ADMIN_ACTION_ACCESS_LOCATION);
+                strings.add(ADMIN_ACTION_ACCESS_MICROPHONE);
+                strings.add(ADMIN_ACTION_ACCESS_CAMERA);
+                strings.add(ADMIN_ACTION_SET_DEFAULT_APPS);
+                strings.add(ADMIN_ACTIONS_APPS_COUNT);
+                strings.add(ADMIN_ACTION_SET_CURRENT_INPUT_METHOD);
+                strings.add(ADMIN_ACTION_SET_INPUT_METHOD_NAME);
+                strings.add(ADMIN_ACTION_SET_HTTP_PROXY);
+                strings.add(WORK_PROFILE_PRIVACY_POLICY_INFO_SUMMARY);
+                strings.add(WORK_PROFILE_PRIVACY_POLICY_INFO);
+                strings.add(CONNECTED_APPS_SEARCH_KEYWORDS);
+                strings.add(WORK_PROFILE_UNIFICATION_SEARCH_KEYWORDS);
+                strings.add(ACCOUNTS_SEARCH_KEYWORDS);
+                strings.add(CONTROLLED_BY_ADMIN_SUMMARY);
+                strings.add(WORK_PROFILE_USER_LABEL);
+                strings.add(WORK_CATEGORY_HEADER);
+                strings.add(PERSONAL_CATEGORY_HEADER);
+                return strings;
+            }
+        }
+
+        /**
+         * Class containing the identifiers used to update device management-related system strings
          * in the Launcher package.
          *
          * @hide
          */
         public static final class Launcher {
 
-            private Launcher(){}
+            private Launcher() {
+            }
 
             private static final String PREFIX = "Launcher.";
 
@@ -576,6 +2016,7 @@
 
             private SystemUi() {
             }
+
             private static final String PREFIX = "SystemUi.";
 
             /**
@@ -649,9 +2090,9 @@
                     PREFIX + "QS_MSG_NAMED_WORK_PROFILE_MONITORING";
 
             /**
-            * Disclosure at the bottom of Quick Settings to indicate network activity is visible to
+             * Disclosure at the bottom of Quick Settings to indicate network activity is visible to
              * admin.
-            */
+             */
             public static final String QS_MSG_WORK_PROFILE_NETWORK =
                     PREFIX + "QS_MSG_WORK_PROFILE_NETWORK";
 
diff --git a/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java b/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java
index 8c232c0..1f7ae4a 100644
--- a/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java
+++ b/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java
@@ -25,6 +25,7 @@
 import android.content.ComponentName;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.PersistableBundle;
 import android.stats.devicepolicy.DevicePolicyEnums;
 
 import java.util.Locale;
@@ -52,6 +53,7 @@
     @SuppressLint("UseIcu")
     @Nullable private final Locale mLocale;
     private final boolean mDeviceOwnerCanGrantSensorsPermissions;
+    @NonNull private final PersistableBundle mAdminExtras;
 
     private FullyManagedDeviceProvisioningParams(
             @NonNull ComponentName deviceAdminComponentName,
@@ -60,7 +62,8 @@
             @Nullable String timeZone,
             long localTime,
             @Nullable @SuppressLint("UseIcu") Locale locale,
-            boolean deviceOwnerCanGrantSensorsPermissions) {
+            boolean deviceOwnerCanGrantSensorsPermissions,
+            @NonNull PersistableBundle adminExtras) {
         this.mDeviceAdminComponentName = requireNonNull(deviceAdminComponentName);
         this.mOwnerName = requireNonNull(ownerName);
         this.mLeaveAllSystemAppsEnabled = leaveAllSystemAppsEnabled;
@@ -69,6 +72,7 @@
         this.mLocale = locale;
         this.mDeviceOwnerCanGrantSensorsPermissions =
                 deviceOwnerCanGrantSensorsPermissions;
+        this.mAdminExtras = adminExtras;
     }
 
     private FullyManagedDeviceProvisioningParams(
@@ -78,14 +82,16 @@
             @Nullable String timeZone,
             long localTime,
             @Nullable String localeStr,
-            boolean deviceOwnerCanGrantSensorsPermissions) {
+            boolean deviceOwnerCanGrantSensorsPermissions,
+            @Nullable PersistableBundle adminExtras) {
         this(deviceAdminComponentName,
                 ownerName,
                 leaveAllSystemAppsEnabled,
                 timeZone,
                 localTime,
                 getLocale(localeStr),
-                deviceOwnerCanGrantSensorsPermissions);
+                deviceOwnerCanGrantSensorsPermissions,
+                adminExtras);
     }
 
     @Nullable
@@ -151,6 +157,15 @@
     }
 
     /**
+     * Returns a copy of the admin extras bundle.
+     *
+     * @see DevicePolicyManager#EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE
+     */
+    public @NonNull PersistableBundle getAdminExtras() {
+        return new PersistableBundle(mAdminExtras);
+    }
+
+    /**
      * Logs the provisioning params using {@link DevicePolicyEventLogger}.
      *
      * @hide
@@ -188,6 +203,7 @@
         @Nullable private Locale mLocale;
         // Default to allowing control over sensor permission grants.
         boolean mDeviceOwnerCanGrantSensorsPermissions = true;
+        @NonNull private PersistableBundle mAdminExtras;
 
         /**
          * Initialize a new {@link Builder} to construct a
@@ -262,6 +278,17 @@
         }
 
         /**
+         * Sets a {@link PersistableBundle} that contains admin-specific extras.
+         */
+        @NonNull
+        public Builder setAdminExtras(@NonNull PersistableBundle adminExtras) {
+            mAdminExtras = adminExtras != null
+                    ? new PersistableBundle(adminExtras)
+                    : new PersistableBundle();
+            return this;
+        }
+
+        /**
          * Combines all of the attributes that have been set on this {@code Builder}
          *
          * @return a new {@link FullyManagedDeviceProvisioningParams} object.
@@ -275,7 +302,8 @@
                     mTimeZone,
                     mLocalTime,
                     mLocale,
-                    mDeviceOwnerCanGrantSensorsPermissions);
+                    mDeviceOwnerCanGrantSensorsPermissions,
+                    mAdminExtras);
         }
     }
 
@@ -298,6 +326,7 @@
                 + ", mLocale=" + (mLocale == null ? "null" : mLocale)
                 + ", mDeviceOwnerCanGrantSensorsPermissions="
                 + mDeviceOwnerCanGrantSensorsPermissions
+                + ", mAdminExtras=" + mAdminExtras
                 + '}';
     }
 
@@ -310,6 +339,7 @@
         dest.writeLong(mLocalTime);
         dest.writeString(mLocale == null ? null : mLocale.toLanguageTag());
         dest.writeBoolean(mDeviceOwnerCanGrantSensorsPermissions);
+        dest.writePersistableBundle(mAdminExtras);
     }
 
     @NonNull
@@ -324,6 +354,7 @@
                     long localtime = in.readLong();
                     String locale = in.readString();
                     boolean deviceOwnerCanGrantSensorsPermissions = in.readBoolean();
+                    PersistableBundle adminExtras = in.readPersistableBundle();
 
                     return new FullyManagedDeviceProvisioningParams(
                             componentName,
@@ -332,7 +363,8 @@
                             timeZone,
                             localtime,
                             locale,
-                            deviceOwnerCanGrantSensorsPermissions);
+                            deviceOwnerCanGrantSensorsPermissions,
+                            adminExtras);
                 }
 
                 @Override
diff --git a/core/java/android/app/admin/ManagedProfileProvisioningParams.java b/core/java/android/app/admin/ManagedProfileProvisioningParams.java
index ccbef73..f91d60a 100644
--- a/core/java/android/app/admin/ManagedProfileProvisioningParams.java
+++ b/core/java/android/app/admin/ManagedProfileProvisioningParams.java
@@ -23,8 +23,10 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.content.ComponentName;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.PersistableBundle;
 import android.stats.devicepolicy.DevicePolicyEnums;
 
 /**
@@ -49,7 +51,7 @@
     private final boolean mLeaveAllSystemAppsEnabled;
     private final boolean mOrganizationOwnedProvisioning;
     private final boolean mKeepAccountOnMigration;
-
+    @NonNull private final PersistableBundle mAdminExtras;
 
     private ManagedProfileProvisioningParams(
             @NonNull ComponentName profileAdminComponentName,
@@ -58,7 +60,8 @@
             @Nullable Account accountToMigrate,
             boolean leaveAllSystemAppsEnabled,
             boolean organizationOwnedProvisioning,
-            boolean keepAccountOnMigration) {
+            boolean keepAccountOnMigration,
+            @NonNull PersistableBundle adminExtras) {
         this.mProfileAdminComponentName = requireNonNull(profileAdminComponentName);
         this.mOwnerName = requireNonNull(ownerName);
         this.mProfileName = profileName;
@@ -66,6 +69,7 @@
         this.mLeaveAllSystemAppsEnabled = leaveAllSystemAppsEnabled;
         this.mOrganizationOwnedProvisioning = organizationOwnedProvisioning;
         this.mKeepAccountOnMigration = keepAccountOnMigration;
+        this.mAdminExtras = adminExtras;
     }
 
     /**
@@ -124,6 +128,15 @@
     }
 
     /**
+     * Returns a copy of the admin extras bundle.
+     *
+     * @see DevicePolicyManager#EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE
+     */
+    public @NonNull PersistableBundle getAdminExtras() {
+        return new PersistableBundle(mAdminExtras);
+    }
+
+    /**
      * Logs the provisioning params using {@link DevicePolicyEventLogger}.
      *
      * @hide
@@ -160,6 +173,7 @@
         private boolean mLeaveAllSystemAppsEnabled;
         private boolean mOrganizationOwnedProvisioning;
         private boolean mKeepingAccountOnMigration;
+        @Nullable private PersistableBundle mAdminExtras;
 
         /**
          * Initialize a new {@link Builder) to construct a {@link ManagedProfileProvisioningParams}.
@@ -235,6 +249,17 @@
         }
 
         /**
+         * Sets a {@link Bundle} that contains admin-specific extras.
+         */
+        @NonNull
+        public Builder setAdminExtras(@NonNull PersistableBundle adminExtras) {
+            mAdminExtras = adminExtras != null
+                    ? new PersistableBundle(adminExtras)
+                    : new PersistableBundle();
+            return this;
+        }
+
+        /**
          * Combines all of the attributes that have been set on this {@code Builder}.
          *
          * @return a new {@link ManagedProfileProvisioningParams} object.
@@ -248,7 +273,8 @@
                     mAccountToMigrate,
                     mLeaveAllSystemAppsEnabled,
                     mOrganizationOwnedProvisioning,
-                    mKeepingAccountOnMigration);
+                    mKeepingAccountOnMigration,
+                    mAdminExtras);
         }
     }
 
@@ -270,6 +296,7 @@
                 + ", mLeaveAllSystemAppsEnabled=" + mLeaveAllSystemAppsEnabled
                 + ", mOrganizationOwnedProvisioning=" + mOrganizationOwnedProvisioning
                 + ", mKeepAccountOnMigration=" + mKeepAccountOnMigration
+                + ", mAdminExtras=" + mAdminExtras
                 + '}';
     }
 
@@ -282,6 +309,7 @@
         dest.writeBoolean(mLeaveAllSystemAppsEnabled);
         dest.writeBoolean(mOrganizationOwnedProvisioning);
         dest.writeBoolean(mKeepAccountOnMigration);
+        dest.writePersistableBundle(mAdminExtras);
     }
 
     public static final @NonNull Creator<ManagedProfileProvisioningParams> CREATOR =
@@ -295,6 +323,7 @@
                     boolean leaveAllSystemAppsEnabled = in.readBoolean();
                     boolean organizationOwnedProvisioning = in.readBoolean();
                     boolean keepAccountMigrated = in.readBoolean();
+                    PersistableBundle adminExtras = in.readPersistableBundle();
 
                     return new ManagedProfileProvisioningParams(
                             componentName,
@@ -303,7 +332,8 @@
                             account,
                             leaveAllSystemAppsEnabled,
                             organizationOwnedProvisioning,
-                            keepAccountMigrated);
+                            keepAccountMigrated,
+                            adminExtras);
                 }
 
                 @Override
diff --git a/core/java/android/app/admin/ParcelableResource.java b/core/java/android/app/admin/ParcelableResource.java
index dba3628..0b1b166 100644
--- a/core/java/android/app/admin/ParcelableResource.java
+++ b/core/java/android/app/admin/ParcelableResource.java
@@ -175,7 +175,7 @@
      * <p>Returns the default drawable by calling the {@code defaultDrawableLoader} if the updated
      * drawable was not found or could not be loaded.</p>
      */
-    @NonNull
+    @Nullable
     public Drawable getDrawable(
             Context context,
             int density,
@@ -200,7 +200,7 @@
      * <p>Returns the default string by calling  {@code defaultStringLoader} if the updated
      * string was not found or could not be loaded.</p>
      */
-    @NonNull
+    @Nullable
     public String getString(
             Context context,
             @NonNull Callable<String> defaultStringLoader) {
@@ -267,17 +267,11 @@
     /**
      * returns the {@link Drawable} loaded from calling {@code defaultDrawableLoader}.
      */
-    @NonNull
+    @Nullable
     public static Drawable loadDefaultDrawable(@NonNull Callable<Drawable> defaultDrawableLoader) {
         try {
             Objects.requireNonNull(defaultDrawableLoader, "defaultDrawableLoader can't be null");
-
-            Drawable drawable = defaultDrawableLoader.call();
-            Objects.requireNonNull(drawable, "defaultDrawable can't be null");
-
-            return drawable;
-        } catch (NullPointerException rethrown) {
-            throw rethrown;
+            return defaultDrawableLoader.call();
         } catch (Exception e) {
             throw new RuntimeException("Couldn't load default drawable: ", e);
         }
@@ -286,17 +280,11 @@
     /**
      * returns the {@link String} loaded from calling {@code defaultStringLoader}.
      */
-    @NonNull
+    @Nullable
     public static String loadDefaultString(@NonNull Callable<String> defaultStringLoader) {
         try {
             Objects.requireNonNull(defaultStringLoader, "defaultStringLoader can't be null");
-
-            String string = defaultStringLoader.call();
-            Objects.requireNonNull(string, "defaultString can't be null");
-
-            return string;
-        } catch (NullPointerException rethrown) {
-            throw rethrown;
+            return defaultStringLoader.call();
         } catch (Exception e) {
             throw new RuntimeException("Couldn't load default string: ", e);
         }
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 0b8a8a2..a0864d6 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -37,6 +37,7 @@
 import android.annotation.UiContext;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
+import android.app.BroadcastOptions;
 import android.app.GameManager;
 import android.app.IApplicationThread;
 import android.app.IServiceConnection;
@@ -2260,6 +2261,27 @@
     }
 
     /**
+     * Version of {@link #sendBroadcastMultiplePermissions(Intent, String[])} that allows you to
+     * specify the {@link android.app.BroadcastOptions}.
+     *
+     * @param intent The Intent to broadcast; all receivers matching this
+     *               Intent will receive the broadcast.
+     * @param receiverPermissions Array of names of permissions that a receiver must hold
+     *                            in order to receive your broadcast.
+     *                            If empty, no permissions are required.
+     * @param options Additional sending options, generated from a
+     *                {@link android.app.BroadcastOptions}.
+     * @see #sendBroadcastMultiplePermissions(Intent, String[])
+     * @see android.app.BroadcastOptions
+     * @hide
+     */
+    @SystemApi
+    public void sendBroadcastMultiplePermissions(@NonNull Intent intent,
+            @NonNull String[] receiverPermissions, @Nullable BroadcastOptions options) {
+       sendBroadcastMultiplePermissions(intent, receiverPermissions, options.toBundle());
+    }
+
+    /**
      * Broadcast the given intent to all interested BroadcastReceivers, allowing
      * an array of required permissions to be enforced.  This call is asynchronous; it returns
      * immediately, and you will continue executing while the receivers are run.  No results are
@@ -5636,6 +5658,15 @@
     public static final String OVERLAY_SERVICE = "overlay";
 
     /**
+     * Use with {@link #getSystemService(String)} to manage resources.
+     *
+     * @see #getSystemService(String)
+     * @see com.android.server.resources.ResourcesManagerService
+     * @hide
+     */
+    public static final String RESOURCES_SERVICE = "resources";
+
+    /**
      * Use with {@link #getSystemService(String)} to retrieve a
      * {android.os.IIdmap2} for managing idmap files (used by overlay
      * packages).
@@ -6675,21 +6706,27 @@
             @NonNull Configuration overrideConfiguration);
 
     /**
-     * Returns a new <code>Context</code> object from the current context but with resources
-     * adjusted to match the metrics of <code>display</code>. Each call to this method
+     * Returns a new {@code Context} object from the current context but with resources
+     * adjusted to match the metrics of {@code display}. Each call to this method
      * returns a new instance of a context object. Context objects are not shared; however,
      * common state (such as the {@link ClassLoader} and other resources for the same
-     * configuration) can be shared, so the <code>Context</code> itself is lightweight.
+     * configuration) can be shared, so the {@code Context} itself is lightweight.
+     *
+     * <p><b>Note:</b>
+     * This {@code Context} is <b>not</b> expected to be updated with new configuration if the
+     * underlying display configuration changes and the cached {@code Resources} it returns
+     * could be stale. It is suggested to use
+     * {@link android.hardware.display.DisplayManager.DisplayListener} to listen for
+     * changes and re-create an instance if necessary. </p>
      * <p>
+     * This {@code Context} is <b>not</b> a UI context, do not use it to access UI components
+     * or obtain a {@link WindowManager} instance.
+     * </p><p>
      * To obtain an instance of {@link WindowManager} configured to show windows on the given
      * display, call {@link #createWindowContext(int, Bundle)} on the returned display context,
      * then call {@link #getSystemService(String)} or {@link #getSystemService(Class)} on the
      * returned window context.
-     * <p>
-     * <b>Note:</b> The context returned by <code>createDisplayContext(Display)</code> is not a UI
-     * context. Do not access UI components or obtain a {@link WindowManager} from the context
-     * created by <code>createDisplayContext(Display)</code>.
-     *
+     * </p>
      * @param display The display to which the current context's resources are adjusted.
      *
      * @return A context for the display.
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index fb186fd..3e527f8 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3986,7 +3986,7 @@
      * {@link android.Manifest.permission#MANAGE_USERS} to receive this broadcast.
      * @hide
      */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    @SystemApi
     public static final String ACTION_USER_SWITCHED =
             "android.intent.action.USER_SWITCHED";
 
@@ -6196,6 +6196,8 @@
      *
      * @hide
      */
+    @SystemApi
+    @SuppressLint("ActionValue")
     public static final String EXTRA_USER_HANDLE =
             "android.intent.extra.user_handle";
 
@@ -7059,6 +7061,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static final int FLAG_RECEIVER_INCLUDE_BACKGROUND = 0x01000000;
     /**
      * If set, the broadcast will never go to manifest receivers in background (cached
diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java
index 1e88758..94f0561 100644
--- a/core/java/android/content/pm/CrossProfileApps.java
+++ b/core/java/android/content/pm/CrossProfileApps.java
@@ -282,7 +282,8 @@
         final boolean isManagedProfile =
                 mUserManager.isManagedProfile(userHandle.getIdentifier());
         if (isManagedProfile) {
-            return mResources.getDrawable(R.drawable.ic_corp_badge, null);
+            return mContext.getPackageManager().getUserBadgeForDensityNoBackground(
+                    userHandle, /* density= */ 0);
         } else {
             return UserIcons.getDefaultUserIcon(
                     mResources, UserHandle.USER_SYSTEM, true /* light */);
diff --git a/core/java/android/content/pm/PackageInfoLite.java b/core/java/android/content/pm/PackageInfoLite.java
index 9735f81..410e106 100644
--- a/core/java/android/content/pm/PackageInfoLite.java
+++ b/core/java/android/content/pm/PackageInfoLite.java
@@ -21,7 +21,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
 
 /**
  * Basic information about a package as specified in its manifest.
@@ -80,10 +80,10 @@
 
     /**
      * Specifies the recommended install location. Can be one of
-     * {@link PackageHelper#RECOMMEND_INSTALL_INTERNAL} to install on internal storage,
-     * {@link PackageHelper#RECOMMEND_INSTALL_EXTERNAL} to install on external media,
-     * {@link PackageHelper#RECOMMEND_FAILED_INSUFFICIENT_STORAGE} for storage errors,
-     * or {@link PackageHelper#RECOMMEND_FAILED_INVALID_APK} for parse errors.
+     * {@link InstallLocationUtils#RECOMMEND_INSTALL_INTERNAL} to install on internal storage,
+     * {@link InstallLocationUtils#RECOMMEND_INSTALL_EXTERNAL} to install on external media,
+     * {@link InstallLocationUtils#RECOMMEND_FAILED_INSUFFICIENT_STORAGE} for storage errors,
+     * or {@link InstallLocationUtils#RECOMMEND_FAILED_INVALID_APK} for parse errors.
      */
     public int recommendedInstallLocation;
     public int installLocation;
diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING
index a503d14..dea0834 100644
--- a/core/java/android/content/pm/TEST_MAPPING
+++ b/core/java/android/content/pm/TEST_MAPPING
@@ -162,5 +162,68 @@
     {
       "name": "CtsInstallHostTestCases"
     }
+  ],
+  "staged-platinum-postsubmit": [
+    {
+      "name": "CtsIncrementalInstallHostTestCases"
+    },
+    {
+      "name": "CtsInstallHostTestCases"
+    },
+    {
+      "name": "CtsStagedInstallHostTestCases"
+    },
+    {
+      "name": "CtsExtractNativeLibsHostTestCases"
+    },
+    {
+      "name": "CtsAppSecurityHostTestCases",
+      "options": [
+        {
+          "include-filter": "com.android.cts.splitapp.SplitAppTest"
+        },
+        {
+          "include-filter": "android.appsecurity.cts.EphemeralTest"
+        }
+      ]
+    },
+    {
+      "name": "FrameworksServicesTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.pm.PackageParserTest"
+        }
+      ]
+    },
+    {
+      "name": "CtsRollbackManagerHostTestCases"
+    },
+    {
+      "name": "CtsOsHostTestCases",
+      "options": [
+        {
+          "include-filter": "com.android.server.pm.PackageParserTest"
+        }
+      ]
+    },
+    {
+      "name": "CtsContentTestCases",
+      "options": [
+        {
+          "include-filter": "android.content.cts.IntentFilterTest"
+        }
+      ]
+    },
+    {
+      "name": "CtsAppEnumerationTestCases"
+    },
+    {
+      "name": "PackageManagerServiceUnitTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.pm.test.verify.domain"
+        }
+      ]
+    }
   ]
 }
diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java
index 6fd2d05..7a5ac8e 100644
--- a/core/java/android/content/res/ApkAssets.java
+++ b/core/java/android/content/res/ApkAssets.java
@@ -28,6 +28,7 @@
 
 import java.io.FileDescriptor;
 import java.io.IOException;
+import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
@@ -438,6 +439,12 @@
         }
     }
 
+    void dump(PrintWriter pw, String prefix) {
+        pw.println(prefix + "class=" + getClass());
+        pw.println(prefix + "debugName=" + getDebugName());
+        pw.println(prefix + "assetPath=" + getAssetPath());
+    }
+
     private static native long nativeLoad(@FormatType int format, @NonNull String path,
             @PropertyFlags int flags, @Nullable AssetsProvider asset) throws IOException;
     private static native long nativeLoadEmpty(@PropertyFlags int flags,
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index bfd9fd0..a05f5c9 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -43,6 +43,7 @@
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.PrintWriter;
 import java.lang.ref.Reference;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -1531,6 +1532,15 @@
         }
     }
 
+    synchronized void dump(PrintWriter pw, String prefix) {
+        pw.println(prefix + "class=" + getClass());
+        pw.println(prefix + "apkAssets=");
+        for (int i = 0; i < mApkAssets.length; i++) {
+            pw.println(prefix + i);
+            mApkAssets[i].dump(pw, prefix + "  ");
+        }
+    }
+
     // AssetManager setup native methods.
     private static native long nativeCreate();
     private static native void nativeDestroy(long ptr);
diff --git a/core/java/android/content/res/IResourcesManager.aidl b/core/java/android/content/res/IResourcesManager.aidl
new file mode 100644
index 0000000..d137378
--- /dev/null
+++ b/core/java/android/content/res/IResourcesManager.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.res;
+
+import android.os.RemoteCallback;
+
+/**
+ * Api for getting information about resources.
+ *
+ * {@hide}
+ */
+interface IResourcesManager {
+    boolean dumpResources(in String process,
+        in ParcelFileDescriptor fd,
+        in RemoteCallback finishCallback);
+}
\ No newline at end of file
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 5fd0d84..ebef053 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -53,6 +53,7 @@
 import android.graphics.drawable.DrawableInflater;
 import android.os.Build;
 import android.os.Bundle;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
@@ -78,11 +79,15 @@
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
 
 /**
  * Class for accessing an application's resources.  This sits on top of the
@@ -172,6 +177,11 @@
 
     private int mBaseApkAssetsSize;
 
+    /** @hide */
+    private static Set<Resources> sResourcesHistory = Collections.synchronizedSet(
+            Collections.newSetFromMap(
+                    new WeakHashMap<>()));
+
     /**
      * Returns the most appropriate default theme for the specified target SDK version.
      * <ul>
@@ -318,6 +328,7 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public Resources(@Nullable ClassLoader classLoader) {
         mClassLoader = classLoader == null ? ClassLoader.getSystemClassLoader() : classLoader;
+        sResourcesHistory.add(this);
     }
 
     /**
@@ -2649,4 +2660,29 @@
             }
         }
     }
+
+    /** @hide */
+    public void dump(PrintWriter pw, String prefix) {
+        pw.println(prefix + "class=" + getClass());
+        pw.println(prefix + "resourcesImpl");
+        mResourcesImpl.dump(pw, prefix + "  ");
+    }
+
+    /** @hide */
+    public static void dumpHistory(PrintWriter pw, String prefix) {
+        pw.println(prefix + "history");
+        // Putting into a map keyed on the apk assets to deduplicate resources that are different
+        // objects but ultimately represent the same assets
+        Map<List<ApkAssets>, Resources> history = new ArrayMap<>();
+        for (Resources r : sResourcesHistory) {
+            history.put(Arrays.asList(r.mResourcesImpl.mAssets.getApkAssets()), r);
+        }
+        int i = 0;
+        for (Resources r : history.values()) {
+            if (r != null) {
+                pw.println(prefix + i++);
+                r.dump(pw, prefix + "  ");
+            }
+        }
+    }
 }
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 4d850b0c..ff07291 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -61,6 +61,7 @@
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.PrintWriter;
 import java.util.Arrays;
 import java.util.Locale;
 
@@ -1271,6 +1272,12 @@
             NativeAllocationRegistry.createMalloced(ResourcesImpl.class.getClassLoader(),
                     AssetManager.getThemeFreeFunction());
 
+    void dump(PrintWriter pw, String prefix) {
+        pw.println(prefix + "class=" + getClass());
+        pw.println(prefix + "assets");
+        mAssets.dump(pw, prefix + "  ");
+    }
+
     public class ThemeImpl {
         /**
          * Unique key for the series of styles applied to this theme.
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 60f5135..7ff74c6 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -1341,7 +1341,6 @@
      *
      * @hide
      */
-    @SystemApi
     @RequiresPermission(Manifest.permission.MANAGE_USB)
     boolean resetUsbPort(@NonNull UsbPort port, int operationId,
             IUsbOperationInternal callback) {
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index f55c415..223b8cc 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -475,7 +475,7 @@
     private InputMethodPrivilegedOperations mPrivOps = new InputMethodPrivilegedOperations();
 
     @NonNull
-    final NavigationBarController mNavigationBarController =
+    private final NavigationBarController mNavigationBarController =
             new NavigationBarController(this);
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
@@ -1504,7 +1504,7 @@
                 Context.LAYOUT_INFLATER_SERVICE);
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.initSoftInputWindow");
         mWindow = new SoftInputWindow(this, mTheme, mDispatcherState);
-
+        mNavigationBarController.onSoftInputWindowCreated(mWindow);
         {
             final Window window = mWindow.getWindow();
             {
diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java
index 508172d..e5c22e4 100644
--- a/core/java/android/inputmethodservice/NavigationBarController.java
+++ b/core/java/android/inputmethodservice/NavigationBarController.java
@@ -29,6 +29,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.res.Resources;
+import android.graphics.Color;
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.graphics.Region;
@@ -65,6 +66,9 @@
                 @NonNull ViewTreeObserver.InternalInsetsInfo dest) {
         }
 
+        default void onSoftInputWindowCreated(@NonNull SoftInputWindow softInputWindow) {
+        }
+
         default void onViewInitialized() {
         }
 
@@ -78,9 +82,6 @@
                 boolean shouldShowImeSwitcherWhenImeIsShown) {
         }
 
-        default void onSystemBarAppearanceChanged(@Appearance int appearance) {
-        }
-
         default String toDebugString() {
             return "No-op implementation";
         }
@@ -101,6 +102,10 @@
         mImpl.updateTouchableInsets(originalInsets, dest);
     }
 
+    void onSoftInputWindowCreated(@NonNull SoftInputWindow softInputWindow) {
+        mImpl.onSoftInputWindowCreated(softInputWindow);
+    }
+
     void onViewInitialized() {
         mImpl.onViewInitialized();
     }
@@ -117,15 +122,11 @@
         mImpl.setShouldShowImeSwitcherWhenImeIsShown(shouldShowImeSwitcherWhenImeIsShown);
     }
 
-    void onSystemBarAppearanceChanged(@Appearance int appearance) {
-        mImpl.onSystemBarAppearanceChanged(appearance);
-    }
-
     String toDebugString() {
         return mImpl.toDebugString();
     }
 
-    private static final class Impl implements Callback {
+    private static final class Impl implements Callback, Window.DecorCallback {
         private static final int DEFAULT_COLOR_ADAPT_TRANSITION_TIME = 1700;
 
         // Copied from com.android.systemui.animation.Interpolators#LEGACY_DECELERATE
@@ -158,6 +159,8 @@
         @Nullable
         private ValueAnimator mTintAnimator;
 
+        private boolean mDrawLegacyNavigationBarBackground;
+
         Impl(@NonNull InputMethodService inputMethodService) {
             mService = inputMethodService;
         }
@@ -226,9 +229,14 @@
                 mLastInsets = systemInsets;
             }
 
-            mNavigationBarFrame.setBackground(null);
+            if (mDrawLegacyNavigationBarBackground) {
+                mNavigationBarFrame.setBackgroundColor(Color.BLACK);
+            } else {
+                mNavigationBarFrame.setBackground(null);
+            }
 
-            setIconTintInternal(calculateTargetDarkIntensity(mAppearance));
+            setIconTintInternal(calculateTargetDarkIntensity(mAppearance,
+                    mDrawLegacyNavigationBarBackground));
         }
 
         private void uninstallNavigationBarFrameIfNecessary() {
@@ -362,6 +370,13 @@
         }
 
         @Override
+        public void onSoftInputWindowCreated(@NonNull SoftInputWindow softInputWindow) {
+            final Window window = softInputWindow.getWindow();
+            mAppearance = window.getSystemBarAppearance();
+            window.setDecorCallback(this);
+        }
+
+        @Override
         public void onViewInitialized() {
             if (mDestroyed) {
                 return;
@@ -471,7 +486,8 @@
                 return;
             }
 
-            final float targetDarkIntensity = calculateTargetDarkIntensity(mAppearance);
+            final float targetDarkIntensity = calculateTargetDarkIntensity(mAppearance,
+                    mDrawLegacyNavigationBarBackground);
 
             if (mTintAnimator != null) {
                 mTintAnimator.cancel();
@@ -499,18 +515,41 @@
         }
 
         @FloatRange(from = 0.0f, to = 1.0f)
-        private static float calculateTargetDarkIntensity(@Appearance int appearance) {
-            final boolean lightNavBar = (appearance & APPEARANCE_LIGHT_NAVIGATION_BARS) != 0;
+        private static float calculateTargetDarkIntensity(@Appearance int appearance,
+                boolean drawLegacyNavigationBarBackground) {
+            final boolean lightNavBar = !drawLegacyNavigationBarBackground
+                    && (appearance & APPEARANCE_LIGHT_NAVIGATION_BARS) != 0;
             return lightNavBar ? 1.0f : 0.0f;
         }
 
         @Override
+        public boolean onDrawLegacyNavigationBarBackgroundChanged(
+                boolean drawLegacyNavigationBarBackground) {
+            if (mDestroyed) {
+                return false;
+            }
+
+            if (drawLegacyNavigationBarBackground != mDrawLegacyNavigationBarBackground) {
+                mDrawLegacyNavigationBarBackground = drawLegacyNavigationBarBackground;
+                if (mDrawLegacyNavigationBarBackground) {
+                    mNavigationBarFrame.setBackgroundColor(Color.BLACK);
+                } else {
+                    mNavigationBarFrame.setBackground(null);
+                }
+                scheduleRelayout();
+                onSystemBarAppearanceChanged(mAppearance);
+            }
+            return drawLegacyNavigationBarBackground;
+        }
+
+        @Override
         public String toDebugString() {
             return "{mRenderGesturalNavButtons=" + mRenderGesturalNavButtons
                     + " mNavigationBarFrame=" + mNavigationBarFrame
                     + " mShouldShowImeSwitcherWhenImeIsShown" + mShouldShowImeSwitcherWhenImeIsShown
                     + " mAppearance=0x" + Integer.toHexString(mAppearance)
                     + " mDarkIntensity=" + mDarkIntensity
+                    + " mDrawLegacyNavigationBarBackground=" + mDrawLegacyNavigationBarBackground
                     + "}";
         }
     }
diff --git a/core/java/android/inputmethodservice/SoftInputWindow.java b/core/java/android/inputmethodservice/SoftInputWindow.java
index 0893d2a..5704dac 100644
--- a/core/java/android/inputmethodservice/SoftInputWindow.java
+++ b/core/java/android/inputmethodservice/SoftInputWindow.java
@@ -31,7 +31,6 @@
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.WindowInsetsController;
 import android.view.WindowManager;
 
 import java.lang.annotation.Retention;
@@ -264,11 +263,6 @@
         }
     }
 
-    @Override
-    public void onSystemBarAppearanceChanged(@WindowInsetsController.Appearance int appearance) {
-        mService.mNavigationBarController.onSystemBarAppearanceChanged(appearance);
-    }
-
     void dumpDebug(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
         mBounds.dumpDebug(proto, BOUNDS);
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index c936bfa..9122adf 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -523,9 +523,11 @@
      *
      * @param subId the subscriber to get the subscription plans for.
      * @param callingPackage the name of the package making the call.
+     * @return the active {@link SubscriptionPlan}s for the given subscription id, or
+     *         {@code null} if not found.
      * @hide
      */
-    @NonNull
+    @Nullable
     public SubscriptionPlan[] getSubscriptionPlans(int subId, @NonNull String callingPackage) {
         try {
             return mService.getSubscriptionPlans(subId, callingPackage);
@@ -538,7 +540,7 @@
      * Get subscription plan for the given networkTemplate.
      *
      * @param template the networkTemplate to get the subscription plan for.
-     * @return the active {@link SubscriptionPlan} for the given template, or
+     * @return the active {@link SubscriptionPlan}s for the given template, or
      *         {@code null} if not found.
      * @hide
      */
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index df5b7bc..4fe6524 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -21,15 +21,16 @@
 import android.os.ParcelDuration;
 import android.os.PowerSaveState;
 import android.os.WorkSource;
+import android.os.IWakeLockCallback;
 
 /** @hide */
 
 interface IPowerManager
 {
     void acquireWakeLock(IBinder lock, int flags, String tag, String packageName, in WorkSource ws,
-            String historyTag, int displayId);
+            String historyTag, int displayId, IWakeLockCallback callback);
     void acquireWakeLockWithUid(IBinder lock, int flags, String tag, String packageName,
-            int uidtoblame, int displayId);
+            int uidtoblame, int displayId, IWakeLockCallback callback);
     @UnsupportedAppUsage
     void releaseWakeLock(IBinder lock, int flags);
     void updateWakeLockUids(IBinder lock, in int[] uids);
@@ -40,6 +41,7 @@
     boolean setPowerModeChecked(int mode, boolean enabled);
 
     void updateWakeLockWorkSource(IBinder lock, in WorkSource ws, String historyTag);
+    void updateWakeLockCallback(IBinder lock, IWakeLockCallback callback);
     boolean isWakeLockLevelSupported(int level);
 
     void userActivity(int displayId, long time, int event, int flags);
diff --git a/core/java/android/os/IWakeLockCallback.aidl b/core/java/android/os/IWakeLockCallback.aidl
new file mode 100644
index 0000000..89615d2
--- /dev/null
+++ b/core/java/android/os/IWakeLockCallback.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+/**
+ * @hide
+ */
+oneway interface IWakeLockCallback {
+    oneway void onStateChanged(boolean enabled);
+}
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 5bd8588..315eef7 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -2770,6 +2770,23 @@
     public static final int PRE_IDLE_TIMEOUT_MODE_SHORT = 2;
 
     /**
+     * A listener interface to get notified when the wakelock is enabled/disabled.
+     */
+    public interface WakeLockStateListener {
+        /**
+         * Frameworks could disable the wakelock because either device's power allowlist has
+         * changed, or the app's wakelock has exceeded its quota, or the app goes into cached
+         * state.
+         * <p>
+         * This callback is called whenever the wakelock's state has changed.
+         * </p>
+         *
+         * @param enabled true is enabled, false is disabled.
+         */
+        void onStateChanged(boolean enabled);
+    }
+
+    /**
      * A wake lock is a mechanism to indicate that your application needs
      * to have the device stay on.
      * <p>
@@ -2800,6 +2817,8 @@
         private String mHistoryTag;
         private final String mTraceName;
         private final int mDisplayId;
+        private WakeLockStateListener mListener;
+        private IWakeLockCallback mCallback;
 
         private final Runnable mReleaser = () -> release(RELEASE_FLAG_TIMEOUT);
 
@@ -2890,7 +2909,7 @@
                 Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, mTraceName, 0);
                 try {
                     mService.acquireWakeLock(mToken, mFlags, mTag, mPackageName, mWorkSource,
-                            mHistoryTag, mDisplayId);
+                            mHistoryTag, mDisplayId, mCallback);
                 } catch (RemoteException e) {
                     throw e.rethrowFromSystemServer();
                 }
@@ -3083,6 +3102,45 @@
                 }
             };
         }
+
+        /**
+         * Set the listener to get notified when the wakelock is enabled/disabled.
+         *
+         * @param executor {@link Executor} to handle listener callback.
+         * @param listener listener to be added, set the listener to null to cancel a listener.
+         */
+        public void setStateListener(@NonNull @CallbackExecutor Executor executor,
+                @Nullable WakeLockStateListener listener) {
+            Preconditions.checkNotNull(executor, "executor cannot be null");
+            synchronized (mToken) {
+                if (listener != mListener) {
+                    mListener = listener;
+                    if (listener != null) {
+                        mCallback = new IWakeLockCallback.Stub() {
+                            public void onStateChanged(boolean enabled) {
+                                final long token = Binder.clearCallingIdentity();
+                                try {
+                                    executor.execute(() -> {
+                                        listener.onStateChanged(enabled);
+                                    });
+                                } finally {
+                                    Binder.restoreCallingIdentity(token);
+                                }
+                            }
+                        };
+                    } else {
+                        mCallback = null;
+                    }
+                    if (mHeld) {
+                        try {
+                            mService.updateWakeLockCallback(mToken, mCallback);
+                        } catch (RemoteException e) {
+                            throw e.rethrowFromSystemServer();
+                        }
+                    }
+                }
+            }
+        }
     }
 
     /**
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 9598410..21c6487 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -410,9 +410,9 @@
      *
      * <p>The waveform will start the first transition from the vibrator off state, with the
      * resonant frequency by default. To provide an initial state, use
-     * {@link #startWaveform(VibrationParameter)}.
+     * {@link #startWaveform(VibrationEffect.VibrationParameter)}.
      *
-     * @return The {@link VibrationEffect.WaveformBuilder} started with the initial parameters.
+     * @see VibrationEffect.WaveformBuilder
      */
     @NonNull
     public static WaveformBuilder startWaveform() {
@@ -421,14 +421,16 @@
 
     /**
      * Start building a waveform vibration with an initial state specified by a
-     * {@link VibrationParameter}.
+     * {@link VibrationEffect.VibrationParameter}.
      *
      * <p>The waveform builder offers more flexibility for creating waveform vibrations, allowing
      * control over vibration amplitude and frequency via smooth transitions between values.
      *
-     * @param initialParameter The initial {@link VibrationParameter} value to be applied at the
-     *                         beginning of the vibration.
+     * @param initialParameter The initial {@link VibrationEffect.VibrationParameter} value to be
+     *                         applied at the beginning of the vibration.
      * @return The {@link VibrationEffect.WaveformBuilder} started with the initial parameters.
+     *
+     * @see VibrationEffect.WaveformBuilder
      */
     @NonNull
     public static WaveformBuilder startWaveform(@NonNull VibrationParameter initialParameter) {
@@ -439,17 +441,19 @@
 
     /**
      * Start building a waveform vibration with an initial state specified by two
-     * {@link VibrationParameter VibrationParameters}.
+     * {@link VibrationEffect.VibrationParameter VibrationParameters}.
      *
      * <p>The waveform builder offers more flexibility for creating waveform vibrations, allowing
      * control over vibration amplitude and frequency via smooth transitions between values.
      *
-     * @param initialParameter1 The initial {@link VibrationParameter} value to be applied at the
-     *                          beginning of the vibration.
-     * @param initialParameter2 The initial {@link VibrationParameter} value to be applied at the
-     *                          beginning of the vibration, must be a different type of parameter
-     *                          than the one specified by the first argument.
+     * @param initialParameter1 The initial {@link VibrationEffect.VibrationParameter} value to be
+     *                          applied at the beginning of the vibration.
+     * @param initialParameter2 The initial {@link VibrationEffect.VibrationParameter} value to be
+     *                          applied at the beginning of the vibration, must be a different type
+     *                          of parameter than the one specified by the first argument.
      * @return The {@link VibrationEffect.WaveformBuilder} started with the initial parameters.
+     *
+     * @see VibrationEffect.WaveformBuilder
      */
     @NonNull
     public static WaveformBuilder startWaveform(@NonNull VibrationParameter initialParameter1,
@@ -805,7 +809,46 @@
     }
 
     /**
-     * A composition of haptic primitives that, when combined, create a single haptic effect.
+     * A composition of haptic elements that are combined to be playable as a single
+     * {@link VibrationEffect}.
+     *
+     * <p>The haptic primitives are available as {@code Composition.PRIMITIVE_*} constants and
+     * can be added to a composition to create a custom vibration effect. Here is an example of an
+     * effect that grows in intensity and then dies off, with a longer rising portion for emphasis
+     * and an extra tick 100ms after:
+     *
+     * <code>
+     * VibrationEffect effect = VibrationEffect.startComposition()
+     *     .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SLOW_RISE, 0.5f)
+     *     .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL, 0.5f)
+     *     .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1.0f, 100)
+     *     .compose();
+     * </code>
+     *
+     * <p>Composition elements can also be {@link VibrationEffect} instances, including other
+     * compositions, and off durations, which are periods of time when the vibrator will be
+     * turned off. Here is an example of a composition that "warms up" with a light tap,
+     * a stronger double tap, then repeats a vibration pattern indefinitely:
+     *
+     * <code>
+     * VibrationEffect repeatingEffect = VibrationEffect.startComposition()
+     *     .addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK)
+     *     .addOffDuration(Duration.ofMillis(10))
+     *     .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_DOUBLE_CLICK))
+     *     .addOffDuration(Duration.ofMillis(50))
+     *     .addEffect(VibrationEffect.createWaveform(pattern, repeatIndex))
+     *     .compose();
+     * </code>
+     *
+     * <p>When choosing to play a composed effect, you should check that individual components are
+     * supported by the device by using the appropriate vibrator method:
+     *
+     * <ul>
+     *     <li>Primitive support can be checked using {@link Vibrator#arePrimitivesSupported}.
+     *     <li>Effect support can be checked using {@link Vibrator#areEffectsSupported}.
+     *     <li>Amplitude control for one-shot and waveforms with amplitude values can be checked
+     *         using {@link Vibrator#hasAmplitudeControl}.
+     * </ul>
      *
      * @see VibrationEffect#startComposition()
      */
@@ -1091,16 +1134,77 @@
      * A builder for waveform haptic effects.
      *
      * <p>Waveform vibrations constitute of one or more timed transitions to new sets of vibration
-     * parameters. These parameters can be the vibration amplitude or frequency, for example.
+     * parameters. These parameters can be the vibration amplitude, frequency, or both.
+     *
+     * <p>The following example ramps a vibrator turned off to full amplitude at 120Hz, over 100ms
+     * starting at 60Hz, then holds that state for 200ms and ramps back down again over 100ms:
+     *
+     * <code>
+     * import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
+     * import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
+     *
+     * VibrationEffect effect = VibrationEffect.startWaveform(targetFrequency(60))
+     *     .addTransition(Duration.ofMillis(100), targetAmplitude(1), targetFrequency(120))
+     *     .addSustain(Duration.ofMillis(200))
+     *     .addTransition(Duration.ofMillis(100), targetAmplitude(0), targetFrequency(60))
+     *     .build();
+     * </code>
+     *
+     * <p>The initial state of the waveform can be set via
+     * {@link VibrationEffect#startWaveform(VibrationParameter)} or
+     * {@link VibrationEffect#startWaveform(VibrationParameter, VibrationParameter)}. If the initial
+     * parameters are not set then the {@link WaveformBuilder} will start with the vibrator off,
+     * represented by zero amplitude, at the vibrator's resonant frequency.
+     *
+     * <p>Repeating waveforms can be created by building the repeating block separately and adding
+     * it to the end of a composition with
+     * {@link Composition#repeatEffectIndefinitely(VibrationEffect)}:
      *
      * <p>Note that physical vibration actuators have different reaction times for changing
      * amplitude and frequency. Durations specified here represent a timeline for the target
      * parameters, and quality of effects may be improved if the durations allow time for a
      * transition to be smoothly applied.
      *
-     * <p>Repeating waveforms can be built by constructing the repeating block separately and adding
-     * it to the end of a composition using
-     * {@link Composition#repeatEffectIndefinitely(VibrationEffect)}.
+     * <p>The following example illustrates both an initial state and a repeating section, using
+     * a {@link VibrationEffect.Composition}. The resulting effect will have a tick followed by a
+     * repeated beating effect with a rise that stretches out and a sharp finish.
+     *
+     * <code>
+     * VibrationEffect patternToBeRepeated = VibrationEffect.startWaveform(targetAmplitude(0.2f))
+     *     .addSustain(Duration.ofMillis(10))
+     *     .addTransition(Duration.ofMillis(20), targetAmplitude(0.4f))
+     *     .addSustain(Duration.ofMillis(30))
+     *     .addTransition(Duration.ofMillis(40), targetAmplitude(0.8f))
+     *     .addSustain(Duration.ofMillis(50))
+     *     .addTransition(Duration.ofMillis(60), targetAmplitude(0.2f))
+     *     .build();
+     *
+     * VibrationEffect effect = VibrationEffect.startComposition()
+     *     .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+     *     .addOffDuration(Duration.ofMillis(20))
+     *     .repeatEffectIndefinitely(patternToBeRepeated)
+     *     .compose();
+     * </code>
+     *
+     * <p>The amplitude step waveforms that can be created via
+     * {@link VibrationEffect#createWaveform(long[], int[], int)} can also be created with
+     * {@link WaveformBuilder} by adding zero duration transitions:
+     *
+     * <code>
+     * // These two effects are the same
+     * VibrationEffect waveform = VibrationEffect.createWaveform(
+     *     new long[] { 10, 20, 30 },  // timings in milliseconds
+     *     new int[] { 51, 102, 204 }, // amplitudes in [0,255]
+     *     -1);                        // repeat index
+     *
+     * VibrationEffect sameWaveform = VibrationEffect.startWaveform(targetAmplitude(0.2f))
+     *     .addSustain(Duration.ofMillis(10))
+     *     .addTransition(Duration.ZERO, targetAmplitude(0.4f))
+     *     .addSustain(Duration.ofMillis(20))
+     *     .addTransition(Duration.ZERO, targetAmplitude(0.8f))
+     *     .addSustain(Duration.ofMillis(30))
+     *     .build();
+     * </code>
      *
      * @see VibrationEffect#startWaveform
      */
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 4758d83..c4fe1a4 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2436,6 +2436,23 @@
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_MMS_MESSAGE_SETTING = "android.settings.MMS_MESSAGE_SETTING";
 
+    /**
+     * Activity Action: Show a screen of bedtime settings, which is provided by the wellbeing app.
+     * <p>
+     * The handler of this intent action may not exist.
+     * <p>
+     * To start an activity with this intent, apps should set the wellbeing package explicitly in
+     * the intent together with this action. The wellbeing package is defined in
+     * {@code com.android.internal.R.string.config_defaultWellbeingPackage}.
+     * <p>
+     * Output: Nothing
+     *
+     * @hide
+     */
+    @SystemApi
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_BEDTIME_SETTINGS = "android.settings.BEDTIME_SETTINGS";
+
     // End of Intent actions for Settings
 
     /**
@@ -10048,6 +10065,13 @@
         public static final String QS_AUTO_ADDED_TILES = "qs_auto_tiles";
 
         /**
+         * The duration of timeout, in milliseconds, to switch from a non-primary user to the
+         * primary user when the device is docked.
+         * @hide
+         */
+        public static final String TIMEOUT_TO_USER_ZERO = "timeout_to_user_zero";
+
+        /**
          * Backup manager behavioral parameters.
          * This is encoded as a key=value list, separated by commas. Ex:
          *
@@ -10281,6 +10305,15 @@
         public static final String NEARBY_SHARING_SLICE_URI = "nearby_sharing_slice_uri";
 
         /**
+         * Current provider of Fast Pair saved devices page.
+         * Default value in @string/config_defaultNearbyFastPairSettingsDevicesComponent.
+         * No VALIDATOR as this setting will not be backed up.
+         * @hide
+         */
+        public static final String NEARBY_FAST_PAIR_SETTINGS_DEVICES_COMPONENT =
+                "nearby_fast_pair_settings_devices_component";
+
+        /**
          * Controls whether aware is enabled.
          * @hide
          */
diff --git a/core/java/android/service/games/GameService.java b/core/java/android/service/games/GameService.java
index 870a7e3..9df8358 100644
--- a/core/java/android/service/games/GameService.java
+++ b/core/java/android/service/games/GameService.java
@@ -16,9 +16,11 @@
 
 package android.service.games;
 
+import android.Manifest;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SystemApi;
 import android.app.IGameManagerService;
@@ -173,6 +175,7 @@
      *
      * @param taskId The taskId of the game.
      */
+    @RequiresPermission(Manifest.permission.MANAGE_GAME_ACTIVITY)
     public final void createGameSession(@IntRange(from = 0) int taskId) {
         if (mGameServiceController == null) {
             throw new IllegalStateException("Can not call before connected()");
diff --git a/core/java/android/service/games/GameSession.java b/core/java/android/service/games/GameSession.java
index f4baedc..9590933 100644
--- a/core/java/android/service/games/GameSession.java
+++ b/core/java/android/service/games/GameSession.java
@@ -278,7 +278,7 @@
      *
      * @return {@code true} if the game was successfully restarted; otherwise, {@code false}.
      */
-    @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)
+    @RequiresPermission(android.Manifest.permission.MANAGE_GAME_ACTIVITY)
     public final boolean restartGame() {
         try {
             mGameSessionController.restartGame(mTaskId);
diff --git a/core/java/android/view/IDisplayWindowListener.aidl b/core/java/android/view/IDisplayWindowListener.aidl
index 449e9b3..67ae743 100644
--- a/core/java/android/view/IDisplayWindowListener.aidl
+++ b/core/java/android/view/IDisplayWindowListener.aidl
@@ -63,5 +63,5 @@
     /**
      * Called when the keep clear ares on a display have changed.
      */
-    void onKeepClearAreasChanged(int displayId, in List<Rect> keepClearAreas);
+    void onKeepClearAreasChanged(int displayId, in List<Rect> restricted, in List<Rect> unrestricted);
 }
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 039b50a..8401b7c 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -295,6 +295,9 @@
     private OnWindowDismissedCallback mOnWindowDismissedCallback;
     private OnWindowSwipeDismissedCallback mOnWindowSwipeDismissedCallback;
     private WindowControllerCallback mWindowControllerCallback;
+    @WindowInsetsController.Appearance
+    private int mSystemBarAppearance;
+    private DecorCallback mDecorCallback;
     private OnRestrictedCaptionAreaChangedListener mOnRestrictedCaptionAreaChangedListener;
     private Rect mRestrictedCaptionAreaRect;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
@@ -607,17 +610,6 @@
          * @param hasCapture True if the window has pointer capture.
          */
         default public void onPointerCaptureChanged(boolean hasCapture) { };
-
-        /**
-         * Called from
-         * {@link com.android.internal.policy.DecorView#onSystemBarAppearanceChanged(int)}.
-         *
-         * @param appearance The newly applied appearance.
-         * @hide
-         */
-        default void onSystemBarAppearanceChanged(
-                @WindowInsetsController.Appearance int appearance) {
-        }
     }
 
     /** @hide */
@@ -672,6 +664,35 @@
         void updateNavigationBarColor(int color);
     }
 
+    /** @hide */
+    public interface DecorCallback {
+        /**
+         * Called from
+         * {@link com.android.internal.policy.DecorView#onSystemBarAppearanceChanged(int)}.
+         *
+         * @param appearance The newly applied appearance.
+         */
+        void onSystemBarAppearanceChanged(@WindowInsetsController.Appearance int appearance);
+
+        /**
+         * Called from
+         * {@link com.android.internal.policy.DecorView#updateColorViews(WindowInsets, boolean)}
+         * when {@link com.android.internal.policy.DecorView#mDrawLegacyNavigationBarBackground} is
+         * being updated.
+         *
+         * @param drawLegacyNavigationBarBackground the new value that is being set to
+         *        {@link com.android.internal.policy.DecorView#mDrawLegacyNavigationBarBackground}.
+         * @return The value to be set to
+         *   {@link com.android.internal.policy.DecorView#mDrawLegacyNavigationBarBackgroundHandled}
+         *         on behalf of the {@link com.android.internal.policy.DecorView}.
+         *         {@code true} to tell that the Window can render the legacy navigation bar
+         *         background on behalf of the {@link com.android.internal.policy.DecorView}.
+         *         {@code false} to let {@link com.android.internal.policy.DecorView} handle it.
+         */
+        boolean onDrawLegacyNavigationBarBackgroundChanged(
+                boolean drawLegacyNavigationBarBackground);
+    }
+
     /**
      * Callback for clients that want to be aware of where caption draws content.
      */
@@ -996,6 +1017,36 @@
         return mWindowControllerCallback;
     }
 
+    /** @hide */
+    public final void setDecorCallback(DecorCallback decorCallback) {
+        mDecorCallback = decorCallback;
+    }
+
+    /** @hide */
+    @WindowInsetsController.Appearance
+    public final int getSystemBarAppearance() {
+        return mSystemBarAppearance;
+    }
+
+    /** @hide */
+    public final void dispatchOnSystemBarAppearanceChanged(
+            @WindowInsetsController.Appearance int appearance) {
+        mSystemBarAppearance = appearance;
+        if (mDecorCallback != null) {
+            mDecorCallback.onSystemBarAppearanceChanged(appearance);
+        }
+    }
+
+    /** @hide */
+    public final boolean onDrawLegacyNavigationBarBackgroundChanged(
+            boolean drawLegacyNavigationBarBackground) {
+        if (mDecorCallback == null) {
+            return false;
+        }
+        return mDecorCallback.onDrawLegacyNavigationBarBackgroundChanged(
+                drawLegacyNavigationBarBackground);
+    }
+
     /**
      * Set a callback for changes of area where caption will draw its content.
      *
diff --git a/core/java/android/view/WindowCallbackWrapper.java b/core/java/android/view/WindowCallbackWrapper.java
index 115e9e8..02c8945 100644
--- a/core/java/android/view/WindowCallbackWrapper.java
+++ b/core/java/android/view/WindowCallbackWrapper.java
@@ -163,10 +163,5 @@
     public void onPointerCaptureChanged(boolean hasCapture) {
         mWrapped.onPointerCaptureChanged(hasCapture);
     }
-
-    @Override
-    public void onSystemBarAppearanceChanged(@WindowInsetsController.Appearance int appearance) {
-        mWrapped.onSystemBarAppearanceChanged(appearance);
-    }
 }
 
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 41c5401..7c939ac 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -4461,7 +4461,7 @@
      * pixel" units.  This size is adjusted based on the current density and
      * user font size preference.
      *
-     * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op.
+     * <p>Note: if this TextView has the auto-size feature enabled, then this function is no-op.
      *
      * @param size The scaled pixel size.
      *
@@ -4476,7 +4476,7 @@
      * Set the default text size to a given unit and value. See {@link
      * TypedValue} for the possible dimension units.
      *
-     * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op.
+     * <p>Note: if this TextView has the auto-size feature enabled, then this function is no-op.
      *
      * @param unit The desired dimension unit.
      * @param size The desired size in the given units.
diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/InstallLocationUtils.java
similarity index 87%
rename from core/java/com/android/internal/content/PackageHelper.java
rename to core/java/com/android/internal/content/InstallLocationUtils.java
index c2f2052..c456cf3 100644
--- a/core/java/com/android/internal/content/PackageHelper.java
+++ b/core/java/com/android/internal/content/InstallLocationUtils.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.content;
 
+import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
 import static android.os.storage.VolumeInfo.ID_PRIVATE_INTERNAL;
 
 import android.content.Context;
@@ -53,7 +54,7 @@
  * and media container service transports.
  * Some utility methods to invoke StorageManagerService api.
  */
-public class PackageHelper {
+public class InstallLocationUtils {
     public static final int RECOMMEND_INSTALL_INTERNAL = 1;
     public static final int RECOMMEND_INSTALL_EXTERNAL = 2;
     public static final int RECOMMEND_INSTALL_EPHEMERAL = 3;
@@ -89,9 +90,13 @@
      */
     public static abstract class TestableInterface {
         abstract public StorageManager getStorageManager(Context context);
+
         abstract public boolean getForceAllowOnExternalSetting(Context context);
+
         abstract public boolean getAllow3rdPartyOnInternalConfig(Context context);
+
         abstract public ApplicationInfo getExistingAppInfo(Context context, String packageName);
+
         abstract public File getDataDirectory();
     }
 
@@ -150,11 +155,11 @@
     /**
      * Given a requested {@link PackageInfo#installLocation} and calculated
      * install size, pick the actual volume to install the app. Only considers
-     * internal and private volumes, and prefers to keep an existing package on
+     * internal and private volumes, and prefers to keep an existing package onocation
      * its current volume.
      *
      * @return the {@link VolumeInfo#fsUuid} to install onto, or {@code null}
-     *         for internal storage.
+     * for internal storage.
      */
     public static String resolveInstallVolume(Context context, SessionParams params)
             throws IOException {
@@ -316,21 +321,6 @@
                 && params.sizeBytes <= storage.getStorageBytesUntilLow(primary.getPathFile());
     }
 
-    @Deprecated
-    public static int resolveInstallLocation(Context context, String packageName,
-            int installLocation, long sizeBytes, int installFlags) {
-        final SessionParams params = new SessionParams(SessionParams.MODE_INVALID);
-        params.appPackageName = packageName;
-        params.installLocation = installLocation;
-        params.sizeBytes = sizeBytes;
-        params.installFlags = installFlags;
-        try {
-            return resolveInstallLocation(context, params);
-        } catch (IOException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
     /**
      * Given a requested {@link PackageInfo#installLocation} and calculated
      * install size, pick the actual location to install the app.
@@ -393,24 +383,24 @@
             // and will fall through to return INSUFFICIENT_STORAGE
             if (fitsOnInternal) {
                 return (ephemeral)
-                        ? PackageHelper.RECOMMEND_INSTALL_EPHEMERAL
-                        : PackageHelper.RECOMMEND_INSTALL_INTERNAL;
+                        ? InstallLocationUtils.RECOMMEND_INSTALL_EPHEMERAL
+                        : InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL;
             }
         } else if (prefer == RECOMMEND_INSTALL_EXTERNAL) {
             if (fitsOnExternal) {
-                return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
+                return InstallLocationUtils.RECOMMEND_INSTALL_EXTERNAL;
             }
         }
 
         if (checkBoth) {
             if (fitsOnInternal) {
-                return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
+                return InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL;
             } else if (fitsOnExternal) {
-                return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
+                return InstallLocationUtils.RECOMMEND_INSTALL_EXTERNAL;
             }
         }
 
-        return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
+        return InstallLocationUtils.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
     }
 
     @Deprecated
@@ -476,4 +466,48 @@
             return 0;
         }
     }
+
+    public static int installLocationPolicy(int installLocation, int recommendedInstallLocation,
+            int installFlags, boolean installedPkgIsSystem, boolean installedPackageOnExternal) {
+        if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) == 0) {
+            // Invalid install. Return error code
+            return RECOMMEND_FAILED_ALREADY_EXISTS;
+        }
+        // Check for updated system application.
+        if (installedPkgIsSystem) {
+            return RECOMMEND_INSTALL_INTERNAL;
+        }
+        // If current upgrade specifies particular preference
+        if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
+            // Application explicitly specified internal.
+            return RECOMMEND_INSTALL_INTERNAL;
+        } else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
+            // App explicitly prefers external. Let policy decide
+            return recommendedInstallLocation;
+        } else {
+            // Prefer previous location
+            if (installedPackageOnExternal) {
+                return RECOMMEND_INSTALL_EXTERNAL;
+            }
+            return RECOMMEND_INSTALL_INTERNAL;
+        }
+    }
+
+    public static int getInstallationErrorCode(int loc) {
+        if (loc == RECOMMEND_FAILED_INVALID_LOCATION) {
+            return PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
+        } else if (loc == RECOMMEND_FAILED_ALREADY_EXISTS) {
+            return PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
+        } else if (loc == RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
+            return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+        } else if (loc == RECOMMEND_FAILED_INVALID_APK) {
+            return PackageManager.INSTALL_FAILED_INVALID_APK;
+        } else if (loc == RECOMMEND_FAILED_INVALID_URI) {
+            return PackageManager.INSTALL_FAILED_INVALID_URI;
+        } else if (loc == RECOMMEND_MEDIA_UNAVAILABLE) {
+            return PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE;
+        } else {
+            return INSTALL_SUCCEEDED;
+        }
+    }
 }
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 4fc977f..c0fec62 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -2013,17 +2013,15 @@
         private final TimeBase mTimeBase;
         private final LongArrayMultiStateCounter mCounter;
 
-        private TimeInFreqMultiStateCounter(TimeBase timeBase, Parcel in, long timestampMs) {
-            mTimeBase = timeBase;
-            mCounter = LongArrayMultiStateCounter.CREATOR.createFromParcel(in);
-            mCounter.setEnabled(mTimeBase.isRunning(), timestampMs);
-            timeBase.add(this);
-        }
-
         private TimeInFreqMultiStateCounter(TimeBase timeBase, int stateCount, int cpuFreqCount,
                 long timestampMs) {
+            this(timeBase, new LongArrayMultiStateCounter(stateCount, cpuFreqCount), timestampMs);
+        }
+
+        private TimeInFreqMultiStateCounter(TimeBase timeBase, LongArrayMultiStateCounter counter,
+                long timestampMs) {
             mTimeBase = timeBase;
-            mCounter = new LongArrayMultiStateCounter(stateCount, cpuFreqCount);
+            mCounter = counter;
             mCounter.setEnabled(mTimeBase.isRunning(), timestampMs);
             timeBase.add(this);
         }
@@ -2032,6 +2030,19 @@
             mCounter.writeToParcel(out, 0);
         }
 
+        @Nullable
+        private static TimeInFreqMultiStateCounter readFromParcel(Parcel in, TimeBase timeBase,
+                int stateCount, int cpuFreqCount, long timestampMs) {
+            // Read the object from the Parcel, whether it's usable or not
+            LongArrayMultiStateCounter counter =
+                    LongArrayMultiStateCounter.CREATOR.createFromParcel(in);
+            if (counter.getStateCount() != stateCount
+                    || counter.getArrayLength() != cpuFreqCount) {
+                return null;
+            }
+            return new TimeInFreqMultiStateCounter(timeBase, counter, timestampMs);
+        }
+
         @Override
         public void onTimeStarted(long elapsedRealtimeUs, long baseUptimeUs, long baseRealtimeUs) {
             mCounter.setEnabled(true, elapsedRealtimeUs / 1000);
@@ -10784,25 +10795,18 @@
 
             stateCount = in.readInt();
             if (stateCount != 0) {
-                // Read the object from the Parcel, whether it's usable or not
-                TimeInFreqMultiStateCounter counter = new TimeInFreqMultiStateCounter(
-                        mBsi.mOnBatteryTimeBase, in, timestampMs);
-                if (stateCount == PROC_STATE_TIME_COUNTER_STATE_COUNT) {
-                    mProcStateTimeMs = counter;
-                }
+                mProcStateTimeMs = TimeInFreqMultiStateCounter.readFromParcel(in,
+                        mBsi.mOnBatteryTimeBase, PROC_STATE_TIME_COUNTER_STATE_COUNT,
+                        mBsi.getCpuFreqCount(), mBsi.mClock.elapsedRealtime());
             } else {
                 mProcStateTimeMs = null;
             }
 
             stateCount = in.readInt();
             if (stateCount != 0) {
-                // Read the object from the Parcel, whether it's usable or not
-                TimeInFreqMultiStateCounter counter =
-                        new TimeInFreqMultiStateCounter(
-                                mBsi.mOnBatteryScreenOffTimeBase, in, timestampMs);
-                if (stateCount == PROC_STATE_TIME_COUNTER_STATE_COUNT) {
-                    mProcStateScreenOffTimeMs = counter;
-                }
+                mProcStateScreenOffTimeMs = TimeInFreqMultiStateCounter.readFromParcel(in,
+                        mBsi.mOnBatteryScreenOffTimeBase, PROC_STATE_TIME_COUNTER_STATE_COUNT,
+                        mBsi.getCpuFreqCount(), mBsi.mClock.elapsedRealtime());
             } else {
                 mProcStateScreenOffTimeMs = null;
             }
@@ -16361,6 +16365,11 @@
             BATTERY_CHARGED_DELAY_MS = delay >= 0 ? delay : mParser.getInt(
                     KEY_BATTERY_CHARGED_DELAY_MS,
                     DEFAULT_BATTERY_CHARGED_DELAY_MS);
+
+            if (mHandler.hasCallbacks(mDeferSetCharging)) {
+                mHandler.removeCallbacks(mDeferSetCharging);
+                mHandler.postDelayed(mDeferSetCharging, BATTERY_CHARGED_DELAY_MS);
+            }
         }
 
         private void updateKernelUidReadersThrottleTime(long oldTimeMs, long newTimeMs) {
@@ -17160,12 +17169,10 @@
 
             stateCount = in.readInt();
             if (stateCount != 0) {
-                // Read the object from the Parcel, whether it's usable or not
-                TimeInFreqMultiStateCounter counter = new TimeInFreqMultiStateCounter(
-                        mOnBatteryTimeBase, in, mClock.elapsedRealtime());
-                if (stateCount == PROC_STATE_TIME_COUNTER_STATE_COUNT) {
-                    u.mProcStateTimeMs = counter;
-                }
+                detachIfNotNull(u.mProcStateTimeMs);
+                u.mProcStateTimeMs = TimeInFreqMultiStateCounter.readFromParcel(in,
+                        mOnBatteryTimeBase, PROC_STATE_TIME_COUNTER_STATE_COUNT,
+                        getCpuFreqCount(), mClock.elapsedRealtime());
             }
 
             detachIfNotNull(u.mProcStateScreenOffTimeMs);
@@ -17174,13 +17181,9 @@
             stateCount = in.readInt();
             if (stateCount != 0) {
                 detachIfNotNull(u.mProcStateScreenOffTimeMs);
-                // Read the object from the Parcel, whether it's usable or not
-                TimeInFreqMultiStateCounter counter =
-                        new TimeInFreqMultiStateCounter(
-                                mOnBatteryScreenOffTimeBase, in, mClock.elapsedRealtime());
-                if (stateCount == PROC_STATE_TIME_COUNTER_STATE_COUNT) {
-                    u.mProcStateScreenOffTimeMs = counter;
-                }
+                u.mProcStateScreenOffTimeMs = TimeInFreqMultiStateCounter.readFromParcel(in,
+                        mOnBatteryScreenOffTimeBase, PROC_STATE_TIME_COUNTER_STATE_COUNT,
+                        getCpuFreqCount(), mClock.elapsedRealtime());
             }
 
             if (in.readInt() != 0) {
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index a50282e..2925341 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -285,6 +285,7 @@
     private Insets mBackgroundInsets = Insets.NONE;
     private Insets mLastBackgroundInsets = Insets.NONE;
     private boolean mDrawLegacyNavigationBarBackground;
+    private boolean mDrawLegacyNavigationBarBackgroundHandled;
 
     private PendingInsetsController mPendingInsetsController = new PendingInsetsController();
 
@@ -1035,10 +1036,7 @@
     public void onSystemBarAppearanceChanged(@WindowInsetsController.Appearance int appearance) {
         updateColorViews(null /* insets */, true /* animate */);
         if (mWindow != null) {
-            final Window.Callback callback = mWindow.getCallback();
-            if (callback != null) {
-                callback.onSystemBarAppearanceChanged(appearance);
-            }
+            mWindow.dispatchOnSystemBarAppearanceChanged(appearance);
         }
     }
 
@@ -1174,6 +1172,9 @@
             mDrawLegacyNavigationBarBackground = mNavigationColorViewState.visible
                     && (mWindow.getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0;
             if (oldDrawLegacy != mDrawLegacyNavigationBarBackground) {
+                mDrawLegacyNavigationBarBackgroundHandled =
+                        mWindow.onDrawLegacyNavigationBarBackgroundChanged(
+                                mDrawLegacyNavigationBarBackground);
                 if (viewRoot != null) {
                     viewRoot.requestInvalidateRootRenderNode();
                 }
@@ -1266,7 +1267,7 @@
             }
         }
 
-        if (forceConsumingNavBar) {
+        if (forceConsumingNavBar && !mDrawLegacyNavigationBarBackgroundHandled) {
             mBackgroundInsets = Insets.of(mLastLeftInset, 0, mLastRightInset, mLastBottomInset);
         } else {
             mBackgroundInsets = Insets.NONE;
@@ -2491,7 +2492,7 @@
     }
 
     private void drawLegacyNavigationBarBackground(RecordingCanvas canvas) {
-        if (!mDrawLegacyNavigationBarBackground) {
+        if (!mDrawLegacyNavigationBarBackground || mDrawLegacyNavigationBarBackgroundHandled) {
             return;
         }
         View v = mNavigationColorViewState.view;
diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java
index faea7706e..ece6f2f3 100644
--- a/core/java/com/android/internal/policy/TransitionAnimation.java
+++ b/core/java/com/android/internal/policy/TransitionAnimation.java
@@ -29,7 +29,6 @@
 import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_OPEN;
 import static android.view.WindowManager.TRANSIT_OPEN;
 
-import android.annotation.DrawableRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -915,7 +914,7 @@
      * animation.
      */
     public HardwareBuffer createCrossProfileAppsThumbnail(
-            @DrawableRes int thumbnailDrawableRes, Rect frame) {
+            Drawable thumbnailDrawable, Rect frame) {
         final int width = frame.width();
         final int height = frame.height();
 
@@ -924,14 +923,13 @@
         canvas.drawColor(Color.argb(0.6f, 0, 0, 0));
         final int thumbnailSize = mContext.getResources().getDimensionPixelSize(
                 com.android.internal.R.dimen.cross_profile_apps_thumbnail_size);
-        final Drawable drawable = mContext.getDrawable(thumbnailDrawableRes);
-        drawable.setBounds(
+        thumbnailDrawable.setBounds(
                 (width - thumbnailSize) / 2,
                 (height - thumbnailSize) / 2,
                 (width + thumbnailSize) / 2,
                 (height + thumbnailSize) / 2);
-        drawable.setTint(mContext.getColor(android.R.color.white));
-        drawable.draw(canvas);
+        thumbnailDrawable.setTint(mContext.getColor(android.R.color.white));
+        thumbnailDrawable.draw(canvas);
         picture.endRecording();
 
         return Bitmap.createBitmap(picture).getHardwareBuffer();
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 430d84e..8bb9a0a 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -195,7 +195,6 @@
                 "android_util_FileObserver.cpp",
                 "android/opengl/poly_clip.cpp", // TODO: .arm
                 "android/opengl/util.cpp",
-                "android_server_NetworkManagementSocketTagger.cpp",
                 "android_ddm_DdmHandleNativeHeap.cpp",
                 "android_backup_BackupDataInput.cpp",
                 "android_backup_BackupDataOutput.cpp",
@@ -310,6 +309,8 @@
                 "libdl_android",
                 "libtimeinstate",
                 "server_configurable_flags",
+                // TODO: delete when ConnectivityT moves to APEX.
+                "libframework-connectivity-tiramisu-jni",
             ],
             export_shared_lib_headers: [
                 // our headers include libnativewindow's public headers
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index f4296be..cde71cf 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -164,7 +164,6 @@
 extern int register_android_text_Hyphenator(JNIEnv *env);
 extern int register_android_opengl_classes(JNIEnv *env);
 extern int register_android_ddm_DdmHandleNativeHeap(JNIEnv *env);
-extern int register_android_server_NetworkManagementSocketTagger(JNIEnv* env);
 extern int register_android_backup_BackupDataInput(JNIEnv *env);
 extern int register_android_backup_BackupDataOutput(JNIEnv *env);
 extern int register_android_backup_FileBackupHelperBase(JNIEnv *env);
@@ -1623,7 +1622,6 @@
         REG_JNI(register_android_media_midi),
 
         REG_JNI(register_android_opengl_classes),
-        REG_JNI(register_android_server_NetworkManagementSocketTagger),
         REG_JNI(register_android_ddm_DdmHandleNativeHeap),
         REG_JNI(register_android_backup_BackupDataInput),
         REG_JNI(register_android_backup_BackupDataOutput),
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 6b82ba8..edc8c5b 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -204,6 +204,12 @@
 
 jclass gAudioProfileClass;
 jmethodID gAudioProfileCstor;
+static struct {
+    jfieldID mSamplingRates;
+    jfieldID mChannelMasks;
+    jfieldID mChannelIndexMasks;
+    jfieldID mEncapsulationType;
+} gAudioProfileFields;
 
 jclass gVibratorClass;
 static struct {
@@ -1340,81 +1346,50 @@
         jStatus = (jint)AUDIO_JAVA_ERROR;
         goto exit;
     }
+
     for (size_t i = 0; i < nAudioPort->num_audio_profiles; ++i) {
-        size_t numPositionMasks = 0;
-        size_t numIndexMasks = 0;
-        // count up how many masks are positional and indexed
-        for (size_t index = 0; index < nAudioPort->audio_profiles[i].num_channel_masks; index++) {
-            const audio_channel_mask_t mask = nAudioPort->audio_profiles[i].channel_masks[index];
-            if (audio_channel_mask_get_representation(mask) == AUDIO_CHANNEL_REPRESENTATION_INDEX) {
-                numIndexMasks++;
-            } else {
-                numPositionMasks++;
-            }
-        }
-
-        ScopedLocalRef<jintArray> jSamplingRates(env,
-                                                 env->NewIntArray(nAudioPort->audio_profiles[i]
-                                                                          .num_sample_rates));
-        ScopedLocalRef<jintArray> jChannelMasks(env, env->NewIntArray(numPositionMasks));
-        ScopedLocalRef<jintArray> jChannelIndexMasks(env, env->NewIntArray(numIndexMasks));
-        if (!jSamplingRates.get() || !jChannelMasks.get() || !jChannelIndexMasks.get()) {
+        jobject jAudioProfile = nullptr;
+        jStatus = convertAudioProfileFromNative(env, &jAudioProfile, &nAudioPort->audio_profiles[i],
+                                                useInMask);
+        if (jStatus != NO_ERROR) {
             jStatus = (jint)AUDIO_JAVA_ERROR;
             goto exit;
         }
+        env->CallBooleanMethod(jAudioProfiles, gArrayListMethods.add, jAudioProfile);
 
-        if (nAudioPort->audio_profiles[i].num_sample_rates) {
-            env->SetIntArrayRegion(jSamplingRates.get(), 0 /*start*/,
-                                   nAudioPort->audio_profiles[i].num_sample_rates,
-                                   (jint *)nAudioPort->audio_profiles[i].sample_rates);
-        }
-
-        // put the masks in the output arrays
-        for (size_t maskIndex = 0, posMaskIndex = 0, indexedMaskIndex = 0;
-             maskIndex < nAudioPort->audio_profiles[i].num_channel_masks; maskIndex++) {
-            const audio_channel_mask_t mask =
-                    nAudioPort->audio_profiles[i].channel_masks[maskIndex];
-            if (audio_channel_mask_get_representation(mask) == AUDIO_CHANNEL_REPRESENTATION_INDEX) {
-                jint jMask = audio_channel_mask_get_bits(mask);
-                env->SetIntArrayRegion(jChannelIndexMasks.get(), indexedMaskIndex++, 1, &jMask);
-            } else {
-                jint jMask =
-                        useInMask ? inChannelMaskFromNative(mask) : outChannelMaskFromNative(mask);
-                env->SetIntArrayRegion(jChannelMasks.get(), posMaskIndex++, 1, &jMask);
-            }
-        }
-
-        int encapsulationType;
-        if (audioEncapsulationTypeFromNative(nAudioPort->audio_profiles[i].encapsulation_type,
-                                             &encapsulationType) != NO_ERROR) {
-            ALOGW("Unknown encapsualtion type for JAVA API: %u",
-                  nAudioPort->audio_profiles[i].encapsulation_type);
-            continue;
-        }
-
-        ScopedLocalRef<jobject>
-                jAudioProfile(env,
-                              env->NewObject(gAudioProfileClass, gAudioProfileCstor,
-                                             audioFormatFromNative(
-                                                     nAudioPort->audio_profiles[i].format),
-                                             jSamplingRates.get(), jChannelMasks.get(),
-                                             jChannelIndexMasks.get(), encapsulationType));
-        if (jAudioProfile == nullptr) {
-            jStatus = (jint)AUDIO_JAVA_ERROR;
-            goto exit;
-        }
-        env->CallBooleanMethod(jAudioProfiles, gArrayListMethods.add, jAudioProfile.get());
         if (nAudioPort->audio_profiles[i].format == AUDIO_FORMAT_PCM_FLOAT) {
             hasFloat = true;
         } else if (jPcmFloatProfileFromExtendedInteger.get() == nullptr &&
                    audio_is_linear_pcm(nAudioPort->audio_profiles[i].format) &&
                    audio_bytes_per_sample(nAudioPort->audio_profiles[i].format) > 2) {
+            ScopedLocalRef<jintArray>
+                    jSamplingRates(env,
+                                   (jintArray)
+                                           env->GetObjectField(jAudioProfile,
+                                                               gAudioProfileFields.mSamplingRates));
+            ScopedLocalRef<jintArray>
+                    jChannelMasks(env,
+                                  (jintArray)
+                                          env->GetObjectField(jAudioProfile,
+                                                              gAudioProfileFields.mChannelMasks));
+            ScopedLocalRef<jintArray>
+                    jChannelIndexMasks(env,
+                                       (jintArray)env->GetObjectField(jAudioProfile,
+                                                                      gAudioProfileFields
+                                                                              .mChannelIndexMasks));
+            int encapsulationType =
+                    env->GetIntField(jAudioProfile, gAudioProfileFields.mEncapsulationType);
+
             jPcmFloatProfileFromExtendedInteger.reset(
                     env->NewObject(gAudioProfileClass, gAudioProfileCstor,
                                    audioFormatFromNative(AUDIO_FORMAT_PCM_FLOAT),
                                    jSamplingRates.get(), jChannelMasks.get(),
                                    jChannelIndexMasks.get(), encapsulationType));
         }
+
+        if (jAudioProfile != nullptr) {
+            env->DeleteLocalRef(jAudioProfile);
+        }
     }
     if (!hasFloat && jPcmFloatProfileFromExtendedInteger.get() != nullptr) {
         // R and earlier compatibility - add ENCODING_PCM_FLOAT to the end
@@ -3285,6 +3260,14 @@
     jclass audioProfileClass = FindClassOrDie(env, "android/media/AudioProfile");
     gAudioProfileClass = MakeGlobalRefOrDie(env, audioProfileClass);
     gAudioProfileCstor = GetMethodIDOrDie(env, audioProfileClass, "<init>", "(I[I[I[II)V");
+    gAudioProfileFields.mSamplingRates =
+            GetFieldIDOrDie(env, audioProfileClass, "mSamplingRates", "[I");
+    gAudioProfileFields.mChannelMasks =
+            GetFieldIDOrDie(env, audioProfileClass, "mChannelMasks", "[I");
+    gAudioProfileFields.mChannelIndexMasks =
+            GetFieldIDOrDie(env, audioProfileClass, "mChannelIndexMasks", "[I");
+    gAudioProfileFields.mEncapsulationType =
+            GetFieldIDOrDie(env, audioProfileClass, "mEncapsulationType", "I");
 
     jclass audioDescriptorClass = FindClassOrDie(env, "android/media/AudioDescriptor");
     gAudioDescriptorClass = MakeGlobalRefOrDie(env, audioDescriptorClass);
diff --git a/core/jni/android_server_NetworkManagementSocketTagger.cpp b/core/jni/android_server_NetworkManagementSocketTagger.cpp
deleted file mode 100644
index 9734ab9..0000000
--- a/core/jni/android_server_NetworkManagementSocketTagger.cpp
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright 2011, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "NMST_QTagUidNative"
-
-#include <android/multinetwork.h>
-#include <cutils/qtaguid.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <nativehelper/JNIPlatformHelp.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <utils/Log.h>
-#include <utils/misc.h>
-
-#include "jni.h"
-
-namespace android {
-
-static jint tagSocketFd(JNIEnv* env, jclass, jobject fileDescriptor,
-                        jint tagNum, jint uid) {
-  int userFd = jniGetFDFromFileDescriptor(env, fileDescriptor);
-
-  if (env->ExceptionCheck()) {
-    ALOGE("Can't get FileDescriptor num");
-    return (jint)-1;
-  }
-
-  int res = android_tag_socket_with_uid(userFd, tagNum, uid);
-  if (res < 0) {
-    return (jint)-errno;
-  }
-  return (jint)res;
-}
-
-static jint untagSocketFd(JNIEnv* env, jclass, jobject fileDescriptor) {
-  int userFd = jniGetFDFromFileDescriptor(env, fileDescriptor);
-
-  if (env->ExceptionCheck()) {
-    ALOGE("Can't get FileDescriptor num");
-    return (jint)-1;
-  }
-
-  int res = android_untag_socket(userFd);
-  if (res < 0) {
-    return (jint)-errno;
-  }
-  return (jint)res;
-}
-
-static const JNINativeMethod gQTagUidMethods[] = {
-  { "native_tagSocketFd", "(Ljava/io/FileDescriptor;II)I", (void*)tagSocketFd},
-  { "native_untagSocketFd", "(Ljava/io/FileDescriptor;)I", (void*)untagSocketFd},
-};
-
-int register_android_server_NetworkManagementSocketTagger(JNIEnv* env) {
-  return jniRegisterNativeMethods(env, "com/android/server/NetworkManagementSocketTagger", gQTagUidMethods, NELEM(gQTagUidMethods));
-}
-
-};
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 6e54197..9bcd7d2 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -977,61 +977,6 @@
         android:permissionFlags="softRestricted|immutablyRestricted"
         android:protectionLevel="dangerous" />
 
-    <!-- Required to be able to read audio files from shared storage.
-     <p>Protection level: dangerous -->
-    <permission-group android:name="android.permission-group.READ_MEDIA_AURAL"
-                      android:icon="@drawable/perm_group_read_media_aural"
-                      android:label="@string/permgrouplab_readMediaAural"
-                      android:description="@string/permgroupdesc_readMediaAural"
-                      android:priority="950" />
-
-    <!-- Allows an application to read audio files from external storage.
-      <p>This permission is enforced starting in API level
-      {@link android.os.Build.VERSION_CODES#TIRAMISU}.
-      For apps with a <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
-      targetSdkVersion}</a> of {@link android.os.Build.VERSION_CODES#S} or lower, this permission
-      must not be used and the READ_EXTERNAL_STORAGE permission must be used instead.
-     <p>Protection level: dangerous -->
-    <permission android:name="android.permission.READ_MEDIA_AUDIO"
-                android:permissionGroup="android.permission-group.UNDEFINED"
-                android:label="@string/permlab_readMediaAudio"
-                android:description="@string/permdesc_readMediaAudio"
-                android:protectionLevel="dangerous" />
-
-    <!-- Required to be able to read image and video files from shared storage.
-     <p>Protection level: dangerous -->
-    <permission-group android:name="android.permission-group.READ_MEDIA_VISUAL"
-                      android:icon="@drawable/perm_group_read_media_visual"
-                      android:label="@string/permgrouplab_readMediaVisual"
-                      android:description="@string/permgroupdesc_readMediaVisual"
-                      android:priority="1000" />
-
-    <!-- Allows an application to read audio files from external storage.
-    <p>This permission is enforced starting in API level
-    {@link android.os.Build.VERSION_CODES#TIRAMISU}.
-    For apps with a <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
-    targetSdkVersion}</a> of {@link android.os.Build.VERSION_CODES#S} or lower, this permission
-    must not be used and the READ_EXTERNAL_STORAGE permission must be used instead.
-   <p>Protection level: dangerous -->
-    <permission android:name="android.permission.READ_MEDIA_VIDEO"
-                android:permissionGroup="android.permission-group.UNDEFINED"
-                android:label="@string/permlab_readMediaVideo"
-                android:description="@string/permdesc_readMediaVideo"
-                android:protectionLevel="dangerous" />
-
-    <!-- Allows an application to read image files from external storage.
-      <p>This permission is enforced starting in API level
-      {@link android.os.Build.VERSION_CODES#TIRAMISU}.
-      For apps with a <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
-      targetSdkVersion}</a> of {@link android.os.Build.VERSION_CODES#S} or lower, this permission
-      must not be used and the READ_EXTERNAL_STORAGE permission must be used instead.
-     <p>Protection level: dangerous -->
-    <permission android:name="android.permission.READ_MEDIA_IMAGE"
-                android:permissionGroup="android.permission-group.UNDEFINED"
-                android:label="@string/permlab_readMediaImage"
-                android:description="@string/permdesc_readMediaImage"
-                android:protectionLevel="dangerous" />
-
     <!-- Allows an application to write to external storage.
          <p class="note"><strong>Note:</strong> If <em>both</em> your <a
          href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code
@@ -1657,7 +1602,7 @@
         android:protectionLevel="normal"
         android:permissionFlags="removed"/>
 
-    <!-- @hide We need to keep this around for backwards compatibility -->
+    <!-- @SystemApi @hide We need to keep this around for backwards compatibility -->
     <permission android:name="android.permission.WRITE_SMS"
         android:protectionLevel="normal"
         android:permissionFlags="removed"/>
@@ -1735,7 +1680,7 @@
     <permission android:name="android.permission.RECEIVE_EMERGENCY_BROADCAST"
         android:protectionLevel="signature|privileged" />
 
-    <!-- Allows an application to monitor incoming Bluetooth MAP messages, to record
+    <!-- @SystemApi Allows an application to monitor incoming Bluetooth MAP messages, to record
          or perform processing on them. -->
     <!-- @hide -->
     <permission android:name="android.permission.RECEIVE_BLUETOOTH_MAP"
@@ -4682,6 +4627,13 @@
     <permission android:name="android.permission.READ_FRAME_BUFFER"
         android:protectionLevel="signature|recents" />
 
+      <!-- @SystemApi Allows an application to change the touch mode state.
+           Without this permission, an app can only change the touch mode
+           if it currently has focus.
+         @hide -->
+    <permission android:name="android.permission.MODIFY_TOUCH_MODE_STATE"
+        android:protectionLevel="signature" />
+
     <!-- Allows an application to use InputFlinger's low level features.
          @hide -->
     <permission android:name="android.permission.ACCESS_INPUT_FLINGER"
@@ -6174,6 +6126,12 @@
     <permission android:name="android.permission.ACCESS_FPS_COUNTER"
                 android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi Allows the GameService provider to create GameSession and call GameSession
+                    APIs and overlay a view on top of the game's Activity.
+     @hide -->
+    <permission android:name="android.permission.MANAGE_GAME_ACTIVITY"
+                android:protectionLevel="signature|privileged" />
+
     <!-- @SystemApi Allows the holder to register callbacks to inform the RebootReadinessManager
          when they are performing reboot-blocking work.
          @hide -->
@@ -6300,6 +6258,15 @@
     <permission android:name="android.permission.BIND_AMBIENT_CONTEXT_DETECTION_SERVICE"
                 android:protectionLevel="signature" />
 
+    <!-- @SystemApi Allows an app to set keep-clear areas without restrictions on the size or
+        number of keep-clear areas (see {@link android.view.View#setPreferKeepClearRects}).
+        When the system arranges floating windows onscreen, it might decide to ignore keep-clear
+        areas from windows, whose owner does not have this permission.
+        @hide
+    -->
+    <permission android:name="android.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS"
+                android:protectionLevel="signature|privileged" />
+
     <!-- Attribution for Geofencing service. -->
     <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
     <!-- Attribution for Country Detector. -->
diff --git a/core/res/res/drawable-car/car_checkbox.xml b/core/res/res/drawable-car/car_checkbox.xml
deleted file mode 100644
index 083a7aa..0000000
--- a/core/res/res/drawable-car/car_checkbox.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    Copyright 2019 The Android Open Source Project
-
-    Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
-    <item
-        android:width="@*android:dimen/car_primary_icon_size"
-        android:height="@*android:dimen/car_primary_icon_size"
-        android:drawable="@drawable/btn_check_material_anim"/>
-    <item
-        android:width="@*android:dimen/car_primary_icon_size"
-        android:height="@*android:dimen/car_primary_icon_size"
-        android:drawable="@drawable/car_checkbox_background"/>
-</layer-list>
diff --git a/core/res/res/drawable-car/car_checkbox_background.xml b/core/res/res/drawable-car/car_checkbox_background.xml
deleted file mode 100644
index 69dcdbb..0000000
--- a/core/res/res/drawable-car/car_checkbox_background.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    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.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_focused="true" android:state_pressed="true">
-        <shape android:shape="rectangle">
-            <solid android:color="#8A0041BE" />
-            <stroke android:width="4dp" android:color="#0041BE" />
-        </shape>
-    </item>
-    <item android:state_focused="true">
-        <shape android:shape="rectangle">
-            <solid android:color="#3D0059B3" />
-            <stroke android:width="8dp" android:color="#0059B3" />
-        </shape>
-    </item>
-</selector>
diff --git a/core/res/res/drawable/perm_group_read_media_aural.xml b/core/res/res/drawable/perm_group_read_media_aural.xml
deleted file mode 100644
index 6fc9c69..0000000
--- a/core/res/res/drawable/perm_group_read_media_aural.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    Copyright (C) 2015 The Android Open Source Project
-
-    Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24"
-        android:viewportHeight="24"
-        android:tint="?attr/colorControlNormal">
-    <path
-        android:fillColor="@android:color/white"
-        android:pathData="M10,21q-1.65,0 -2.825,-1.175Q6,18.65 6,17q0,-1.65 1.175,-2.825Q8.35,13 10,13q0.575,0 1.063,0.137 0.487,0.138 0.937,0.413V3h6v4h-4v10q0,1.65 -1.175,2.825Q11.65,21 10,21z"/>
-</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/perm_group_read_media_visual.xml b/core/res/res/drawable/perm_group_read_media_visual.xml
deleted file mode 100644
index a5db271..0000000
--- a/core/res/res/drawable/perm_group_read_media_visual.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    Copyright (C) 2015 The Android Open Source Project
-
-    Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24"
-        android:viewportHeight="24"
-        android:tint="?attr/colorControlNormal">
-    <path
-        android:fillColor="@android:color/white"
-        android:pathData="M9,14h10l-3.45,-4.5 -2.3,3 -1.55,-2zM8,18q-0.825,0 -1.412,-0.587Q6,16.825 6,16L6,4q0,-0.825 0.588,-1.413Q7.175,2 8,2h12q0.825,0 1.413,0.587Q22,3.175 22,4v12q0,0.825 -0.587,1.413Q20.825,18 20,18zM8,16h12L20,4L8,4v12zM4,22q-0.825,0 -1.413,-0.587Q2,20.825 2,20L2,6h2v14h14v2zM8,4v12L8,4z"/>
-</vector>
\ No newline at end of file
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index f05f027..a06b2cb 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2612,6 +2612,10 @@
          will be locked. -->
     <bool name="config_multiuserDelayUserDataLocking">false</bool>
 
+    <!-- Whether to automatically switch a non-primary user back to the primary user after a
+         timeout when the device is docked.  -->
+    <bool name="config_enableTimeoutToUserZeroWhenDocked">false</bool>
+
     <!-- Whether to only install system packages on a user if they're allowlisted for that user
          type. These are flags and can be freely combined.
          0  - disable allowlist (install all system packages; no logging)
@@ -4979,6 +4983,10 @@
     <!-- URI used for Nearby Share SliceProvider scanning. -->
     <string translatable="false" name="config_defaultNearbySharingSliceUri"></string>
 
+    <!-- Component name that accepts settings intents for saved devices.
+             Used by FastPairSettingsFragment. -->
+    <string translatable="false" name="config_defaultNearbyFastPairSettingsDevicesComponent"></string>
+
     <!-- Boolean indicating whether frameworks needs to reset cell broadcast geo-fencing
          check after reboot or airplane mode toggling -->
     <bool translatable="false" name="reset_geo_fencing_check_after_boot_or_apm">false</bool>
@@ -5141,6 +5149,9 @@
         If given value is outside of this range, the option 1 (center) is assummed. -->
     <integer name="config_letterboxDefaultPositionForReachability">1</integer>
 
+    <!-- Whether displaying letterbox education is enabled for letterboxed fullscreen apps. -->
+    <bool name="config_letterboxIsEducationEnabled">false</bool>
+
     <!-- Whether a camera compat controller is enabled to allow the user to apply or revert
          treatment for stretched issues in camera viewfinder. -->
     <bool name="config_isCameraCompatControlForStretchedIssuesEnabled">false</bool>
@@ -5642,4 +5653,13 @@
     <!-- The amount of time after becoming non-interactive (in ms) after which
          Low Power Standby can activate. -->
     <integer name="config_lowPowerStandbyNonInteractiveTimeout">5000</integer>
+
+
+    <!-- Mapping to select an Intent.EXTRA_DOCK_STATE value from extcon state
+         key-value pairs. Each entry is evaluated in order and is of the form:
+            "[EXTRA_DOCK_STATE value],key1=value1,key2=value2[,...]"
+         An entry with no key-value pairs is valid and can be used as a wildcard.
+     -->
+    <string-array name="config_dockExtconStateMapping">
+    </string-array>
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 49a12d1..610c6a6 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -880,16 +880,6 @@
     <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permgroupdesc_storage">access photos, media, and files on your device</string>
 
-    <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=40]-->
-    <string name="permgrouplab_readMediaAural">Music &amp; other audio</string>
-    <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE]-->
-    <string name="permgroupdesc_readMediaAural">access audio files on your device</string>
-
-    <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=40]-->
-    <string name="permgrouplab_readMediaVisual">Photos &amp; videos</string>
-    <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE]-->
-    <string name="permgroupdesc_readMediaVisual">access images and video files on your device</string>
-
     <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permgrouplab_microphone">Microphone</string>
     <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
@@ -1903,21 +1893,6 @@
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
     <string name="permdesc_sdcardRead">Allows the app to read the contents of your shared storage.</string>
 
-    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
-    <string name="permlab_readMediaAudio">read audio files from shared storage</string>
-    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
-    <string name="permdesc_readMediaAudio">Allows the app to read audio files from your shared storage.</string>
-
-    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
-    <string name="permlab_readMediaVideo">read video files from shared storage</string>
-    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
-    <string name="permdesc_readMediaVideo">Allows the app to read video files from your shared storage.</string>
-
-    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
-    <string name="permlab_readMediaImage">read image files from shared storage</string>
-    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
-    <string name="permdesc_readMediaImage">Allows the app to read image files from your shared storage.</string>
-
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can write to. [CHAR LIMIT=none] -->
     <string name="permlab_sdcardWrite">modify or delete the contents of your shared storage</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can write to. [CHAR LIMIT=none] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index e1a5eda..4c1cc4d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -466,6 +466,7 @@
   <java-symbol type="integer" name="config_multiuserMaximumUsers" />
   <java-symbol type="integer" name="config_multiuserMaxRunningUsers" />
   <java-symbol type="bool" name="config_multiuserDelayUserDataLocking" />
+  <java-symbol type="bool" name="config_enableTimeoutToUserZeroWhenDocked" />
   <java-symbol type="integer" name="config_userTypePackageWhitelistMode"/>
   <java-symbol type="xml" name="config_user_types" />
   <java-symbol type="integer" name="config_safe_media_volume_index" />
@@ -4100,6 +4101,7 @@
   <java-symbol type="layout" name="chooser_action_button" />
   <java-symbol type="dimen" name="chooser_action_button_icon_size" />
   <java-symbol type="string" name="config_defaultNearbySharingComponent" />
+  <java-symbol type="string" name="config_defaultNearbyFastPairSettingsDevicesComponent" />
   <java-symbol type="bool" name="config_disable_all_cb_messages" />
   <java-symbol type="drawable" name="ic_close" />
 
@@ -4330,6 +4332,7 @@
   <java-symbol type="dimen" name="config_letterboxHorizontalPositionMultiplier" />
   <java-symbol type="bool" name="config_letterboxIsReachabilityEnabled" />
   <java-symbol type="integer" name="config_letterboxDefaultPositionForReachability" />
+  <java-symbol type="bool" name="config_letterboxIsEducationEnabled" />
   <java-symbol type="bool" name="config_isCameraCompatControlForStretchedIssuesEnabled" />
 
   <java-symbol type="bool" name="config_hideDisplayCutoutWithDisplayArea" />
@@ -4677,6 +4680,8 @@
 
   <java-symbol type="string" name="config_deviceSpecificDeviceStatePolicyProvider" />
 
+  <java-symbol type="array" name="config_dockExtconStateMapping" />
+
   <java-symbol type="string" name="notification_channel_abusive_bg_apps"/>
   <java-symbol type="string" name="notification_title_abusive_bg_apps"/>
   <java-symbol type="string" name="notification_content_abusive_bg_apps"/>
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index b2c4274..5c9044c 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -646,6 +646,10 @@
         }
 
         @Override
+        public void dumpResources(ParcelFileDescriptor fd, RemoteCallback finishCallback) {
+        }
+
+        @Override
         public final void runIsolatedEntryPoint(String entryPoint, String[] entryPointArgs) {
         }
 
diff --git a/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java b/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java
index b66642c..fa657f7 100644
--- a/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java
+++ b/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java
@@ -71,6 +71,8 @@
     private Resources mResources;
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private Drawable mDrawable;
+    @Mock
+    private PackageManager mPackageManager;
     private CrossProfileApps mCrossProfileApps;
 
     @Before
@@ -87,6 +89,7 @@
                 Context.DEVICE_POLICY_SERVICE);
         when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(
                 mDevicePolicyManager);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
     }
 
     @Before
@@ -131,7 +134,8 @@
         setValidTargetProfile(MANAGED_PROFILE);
 
         mCrossProfileApps.getProfileSwitchingIconDrawable(MANAGED_PROFILE);
-        verify(mResources).getDrawable(R.drawable.ic_corp_badge, null);
+        verify(mPackageManager).getUserBadgeForDensityNoBackground(
+                MANAGED_PROFILE, /* density= */0);
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/content/pm/PackageHelperTests.java b/core/tests/coretests/src/android/content/pm/InstallLocationUtilsTests.java
similarity index 85%
rename from core/tests/coretests/src/android/content/pm/PackageHelperTests.java
rename to core/tests/coretests/src/android/content/pm/InstallLocationUtilsTests.java
index 947da0b..0629a99 100644
--- a/core/tests/coretests/src/android/content/pm/PackageHelperTests.java
+++ b/core/tests/coretests/src/android/content/pm/InstallLocationUtilsTests.java
@@ -25,7 +25,7 @@
 import android.test.AndroidTestCase;
 import android.util.Log;
 
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
 
 import org.mockito.Mockito;
 
@@ -36,10 +36,9 @@
 import java.util.UUID;
 
 @Presubmit
-public class PackageHelperTests extends AndroidTestCase {
+public class InstallLocationUtilsTests extends AndroidTestCase {
     private static final boolean localLOGV = true;
     public static final String TAG = "PackageHelperTests";
-    protected final String PREFIX = "android.content.pm";
 
     private static final String sInternalVolPath = "/data";
     private static final String sAdoptedVolPath = "/mnt/expand/123";
@@ -88,11 +87,14 @@
         UUID internalUuid = UUID.randomUUID();
         UUID adoptedUuid = UUID.randomUUID();
         UUID publicUuid = UUID.randomUUID();
-        Mockito.when(storageManager.getStorageBytesUntilLow(internalFile)).thenReturn(sInternalSize);
+        Mockito.when(storageManager.getStorageBytesUntilLow(internalFile))
+                .thenReturn(sInternalSize);
         Mockito.when(storageManager.getStorageBytesUntilLow(adoptedFile)).thenReturn(sAdoptedSize);
         Mockito.when(storageManager.getStorageBytesUntilLow(publicFile)).thenReturn(sPublicSize);
-        Mockito.when(storageManager.getUuidForPath(Mockito.eq(internalFile))).thenReturn(internalUuid);
-        Mockito.when(storageManager.getUuidForPath(Mockito.eq(adoptedFile))).thenReturn(adoptedUuid);
+        Mockito.when(storageManager.getUuidForPath(Mockito.eq(internalFile)))
+                .thenReturn(internalUuid);
+        Mockito.when(storageManager.getUuidForPath(Mockito.eq(adoptedFile)))
+                .thenReturn(adoptedUuid);
         Mockito.when(storageManager.getUuidForPath(Mockito.eq(publicFile))).thenReturn(publicUuid);
         Mockito.when(storageManager.getAllocatableBytes(Mockito.eq(internalUuid), Mockito.anyInt()))
                 .thenReturn(sInternalSize);
@@ -103,7 +105,7 @@
         return storageManager;
     }
 
-    private static final class MockedInterface extends PackageHelper.TestableInterface {
+    private static final class MockedInterface extends InstallLocationUtils.TestableInterface {
         private boolean mForceAllowOnExternal = false;
         private boolean mAllow3rdPartyOnInternal = true;
         private ApplicationInfo mApplicationInfo = null;
@@ -164,25 +166,25 @@
         mockedInterface.setMockValues(systemAppInfo, false /*force allow on external*/,
                 true /*allow 3rd party on internal*/);
         String volume = null;
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
             1 /*install location*/, 1000 /*size bytes*/, mockedInterface);
         assertEquals(StorageManager.UUID_PRIVATE_INTERNAL, volume);
 
         mockedInterface.setMockValues(systemAppInfo, true /*force allow on external*/,
                 true /*allow 3rd party on internal*/);
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                 1 /*install location*/, 1000 /*size bytes*/, mockedInterface);
         assertEquals(StorageManager.UUID_PRIVATE_INTERNAL, volume);
 
         mockedInterface.setMockValues(systemAppInfo, false /*force allow on external*/,
                 false /*allow 3rd party on internal*/);
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                 1 /*install location*/, 1000 /*size bytes*/, mockedInterface);
         assertEquals(StorageManager.UUID_PRIVATE_INTERNAL, volume);
 
         mockedInterface.setMockValues(systemAppInfo, true /*force allow on external*/,
                 false /*allow 3rd party on internal*/);
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                 1 /*install location*/, 1000 /*size bytes*/, mockedInterface);
         assertEquals(StorageManager.UUID_PRIVATE_INTERNAL, volume);
 
@@ -192,7 +194,7 @@
         mockedInterface.setMockValues(systemAppInfo, true /*force allow on external*/,
                 true /*allow 3rd party on internal*/);
         try {
-            PackageHelper.resolveInstallVolume(getContext(), "package.name",
+            InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                     1 /*install location*/, 1000000 /*size bytes*/, mockedInterface);
             fail("Expected exception in resolveInstallVolume was not thrown");
         } catch(IOException e) {
@@ -202,7 +204,7 @@
         mockedInterface.setMockValues(systemAppInfo, false /*force allow on external*/,
                 true /*allow 3rd party on internal*/);
         try {
-            PackageHelper.resolveInstallVolume(getContext(), "package.name",
+            InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                     1 /*install location*/, 1000000 /*size bytes*/, mockedInterface);
             fail("Expected exception in resolveInstallVolume was not thrown");
         } catch(IOException e) {
@@ -212,7 +214,7 @@
         mockedInterface.setMockValues(systemAppInfo, false /*force allow on external*/,
                 false /*allow 3rd party on internal*/);
         try {
-            PackageHelper.resolveInstallVolume(getContext(), "package.name",
+            InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                     1 /*install location*/, 1000000 /*size bytes*/, mockedInterface);
             fail("Expected exception in resolveInstallVolume was not thrown");
         } catch(IOException e) {
@@ -222,7 +224,7 @@
         mockedInterface.setMockValues(systemAppInfo, true /*force allow on external*/,
                 false /*allow 3rd party on internal*/);
         try {
-            PackageHelper.resolveInstallVolume(getContext(), "package.name",
+            InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                     1 /*install location*/, 1000000 /*size bytes*/, mockedInterface);
             fail("Expected exception in resolveInstallVolume was not thrown");
         } catch(IOException e) {
@@ -240,13 +242,13 @@
         mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
                 true /*allow 3rd party on internal*/);
         String volume = null;
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                 0 /*install location*/, 1000 /*size bytes*/, mockedInterface);
         assertEquals(sInternalVolUuid, volume);
 
         mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
                 true /*allow 3rd party on internal*/);
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                 0 /*install location*/, 1000 /*size bytes*/, mockedInterface);
         assertEquals(sInternalVolUuid, volume);
     }
@@ -260,25 +262,25 @@
         appInfo.volumeUuid = sAdoptedVolUuid;
         mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
                 true /*allow 3rd party on internal*/);
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
             0 /*install location*/, 1000 /*size bytes*/, mockedInterface);
         assertEquals(sAdoptedVolUuid, volume);
 
         mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
                 true /*allow 3rd party on internal*/);
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                 0 /*install location*/, 1000 /*size bytes*/, mockedInterface);
         assertEquals(sAdoptedVolUuid, volume);
 
         mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
                 false /*allow 3rd party on internal*/);
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                 0 /*install location*/, 1000 /*size bytes*/, mockedInterface);
         assertEquals(sAdoptedVolUuid, volume);
 
         mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
                 false /*allow 3rd party on internal*/);
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                 0 /*install location*/, 1000 /*size bytes*/, mockedInterface);
         assertEquals(sAdoptedVolUuid, volume);
     }
@@ -292,7 +294,7 @@
         mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
                 true /*allow 3rd party on internal*/);
         try {
-            PackageHelper.resolveInstallVolume(getContext(), "package.name",
+            InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                     0 /*install location*/, 10000001 /*BIG size, won't fit*/, mockedInterface);
             fail("Expected exception was not thrown " + appInfo.volumeUuid);
         } catch (IOException e) {
@@ -302,7 +304,7 @@
         mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
                 false /*allow 3rd party on internal*/);
         try {
-            PackageHelper.resolveInstallVolume(getContext(), "package.name",
+            InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                     0 /*install location*/, 10000001 /*BIG size, won't fit*/, mockedInterface);
             fail("Expected exception was not thrown " + appInfo.volumeUuid);
         } catch (IOException e) {
@@ -312,7 +314,7 @@
         mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
                 false /*allow 3rd party on internal*/);
         try {
-            PackageHelper.resolveInstallVolume(getContext(), "package.name",
+            InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                     0 /*install location*/, 10000001 /*BIG size, won't fit*/, mockedInterface);
             fail("Expected exception was not thrown " + appInfo.volumeUuid);
         } catch (IOException e) {
@@ -322,7 +324,7 @@
         mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
                 true /*allow 3rd party on internal*/);
         try {
-            PackageHelper.resolveInstallVolume(getContext(), "package.name",
+            InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                     0 /*install location*/, 10000001 /*BIG size, won't fit*/, mockedInterface);
             fail("Expected exception was not thrown " + appInfo.volumeUuid);
         } catch (IOException e) {
@@ -336,28 +338,28 @@
         mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
                 true /*allow 3rd party on internal*/);
         String volume = null;
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
             0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface);
         // Should return the volume with bigger available space.
         assertEquals(sInternalVolUuid, volume);
 
         mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
                 true /*allow 3rd party on internal*/);
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                 0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface);
         // Should return the volume with bigger available space.
         assertEquals(sInternalVolUuid, volume);
 
         mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
                 false /*allow 3rd party on internal*/);
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                 0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface);
         // Should return the volume with bigger available space.
         assertEquals(sAdoptedVolUuid, volume);
 
         mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
                 false /*allow 3rd party on internal*/);
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                 0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface);
         // Should return the volume with bigger available space.
         assertEquals(sAdoptedVolUuid, volume);
@@ -371,20 +373,20 @@
         mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
                 true /*allow 3rd party on internal*/);
         String volume = null;
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
             1 /*install location internal ONLY*/, 1000 /*size bytes*/, mockedInterface);
         assertEquals(sInternalVolUuid, volume);
 
         mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
                 true /*allow 3rd party on internal*/);
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                 1 /*install location internal ONLY*/, 1000 /*size bytes*/, mockedInterface);
         assertEquals(sInternalVolUuid, volume);
 
         mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
                 false /*allow 3rd party on internal*/);
         try {
-            volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+            volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                     1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface);
             fail("Expected exception in resolveInstallVolume was not thrown");
         } catch (IOException e) {
@@ -395,7 +397,7 @@
         mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
                 false /*allow 3rd party on internal*/);
         volume = null;
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                 1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface);
         assertEquals(sAdoptedVolUuid, volume);
     }
@@ -407,7 +409,7 @@
         mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
                 false /*allow 3rd party on internal*/);
         String volume = null;
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
             0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface);
         // Should return the non-internal volume.
         assertEquals(sAdoptedVolUuid, volume);
@@ -415,7 +417,7 @@
         appInfo = null;
         mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
                 false /*allow 3rd party on internal*/);
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                 0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface);
         // Should return the non-internal volume.
         assertEquals(sAdoptedVolUuid, volume);
@@ -428,7 +430,7 @@
                 true /*allow 3rd party on internal*/);
         String volume = null;
         try {
-            volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+            volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                     1 /*install location internal ONLY*/,
                     1000000 /*size too big*/, mockedInterface);
             fail("Expected exception in resolveInstallVolume was not thrown");
@@ -445,7 +447,7 @@
                 false /*allow 3rd party on internal*/);
         String volume = null;
         try {
-            volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+            volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                     1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface);
             fail("Expected exception in resolveInstallVolume was not thrown");
         } catch (IOException e) {
@@ -456,7 +458,7 @@
         mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
                 false /*allow 3rd party on internal*/);
         volume = null;
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
             1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface);
         assertEquals(sAdoptedVolUuid, volume);
 
@@ -474,7 +476,7 @@
         mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
                 false /*allow 3rd party on internal*/);
         String volume = null;
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
             1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface);
         assertEquals(sAdoptedVolUuid, volume);
     }
diff --git a/core/tests/coretests/src/android/os/VibrationEffectTest.java b/core/tests/coretests/src/android/os/VibrationEffectTest.java
index 104f077..f7ca822 100644
--- a/core/tests/coretests/src/android/os/VibrationEffectTest.java
+++ b/core/tests/coretests/src/android/os/VibrationEffectTest.java
@@ -146,6 +146,7 @@
 
     @Test
     public void testValidateWaveformBuilder() {
+        // Cover builder methods
         VibrationEffect.startWaveform(targetAmplitude(1))
                 .addTransition(Duration.ofSeconds(1), targetAmplitude(0.5f), targetFrequency(100))
                 .addTransition(Duration.ZERO, targetAmplitude(0f), targetFrequency(200))
@@ -158,6 +159,39 @@
                 .build()
                 .validate();
 
+        // Make sure class summary javadoc examples compile and are valid.
+        // NOTE: IF THIS IS UPDATED, PLEASE ALSO UPDATE WaveformBuilder javadocs.
+        VibrationEffect.startWaveform(targetFrequency(60))
+                .addTransition(Duration.ofMillis(100), targetAmplitude(1), targetFrequency(120))
+                .addSustain(Duration.ofMillis(200))
+                .addTransition(Duration.ofMillis(100), targetAmplitude(0), targetFrequency(60))
+                .build()
+                .validate();
+        VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+                .addOffDuration(Duration.ofMillis(20))
+                .repeatEffectIndefinitely(
+                        VibrationEffect.startWaveform(targetAmplitude(0.2f))
+                                .addSustain(Duration.ofMillis(10))
+                                .addTransition(Duration.ofMillis(20), targetAmplitude(0.4f))
+                                .addSustain(Duration.ofMillis(30))
+                                .addTransition(Duration.ofMillis(40), targetAmplitude(0.8f))
+                                .addSustain(Duration.ofMillis(50))
+                                .addTransition(Duration.ofMillis(60), targetAmplitude(0.2f))
+                                .build())
+                .compose()
+                .validate();
+        VibrationEffect.createWaveform(new long[]{10, 20, 30}, new int[]{51, 102, 204}, -1)
+                .validate();
+        VibrationEffect.startWaveform(targetAmplitude(0.2f))
+                .addSustain(Duration.ofMillis(10))
+                .addTransition(Duration.ZERO, targetAmplitude(0.4f))
+                .addSustain(Duration.ofMillis(20))
+                .addTransition(Duration.ZERO, targetAmplitude(0.8f))
+                .addSustain(Duration.ofMillis(30))
+                .build()
+                .validate();
+
         assertThrows(IllegalStateException.class,
                 () -> VibrationEffect.startWaveform().build().validate());
         assertThrows(IllegalArgumentException.class, () -> targetAmplitude(-2));
@@ -171,6 +205,7 @@
 
     @Test
     public void testValidateComposed() {
+        // Cover builder methods
         VibrationEffect.startComposition()
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
                 .addEffect(TEST_ONE_SHOT)
@@ -178,11 +213,28 @@
                 .addOffDuration(Duration.ofMillis(100))
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10)
                 .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .addEffect(VibrationEffect.createWaveform(new long[]{10, 20}, /* repeat= */ 0))
+                .compose()
+                .validate();
+        VibrationEffect.startComposition()
+                .repeatEffectIndefinitely(TEST_ONE_SHOT)
                 .compose()
                 .validate();
 
+        // Make sure class summary javadoc examples compile and are valid.
+        // NOTE: IF THIS IS UPDATED, PLEASE ALSO UPDATE Composition javadocs.
         VibrationEffect.startComposition()
-                .repeatEffectIndefinitely(TEST_ONE_SHOT)
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SLOW_RISE, 0.5f)
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL, 0.5f)
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1.0f, 100)
+                .compose()
+                .validate();
+        VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK)
+                .addOffDuration(Duration.ofMillis(10))
+                .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_DOUBLE_CLICK))
+                .addOffDuration(Duration.ofMillis(50))
+                .addEffect(VibrationEffect.createWaveform(new long[]{10, 20}, /* repeat= */ 0))
                 .compose()
                 .validate();
 
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 88920c8..92fca36 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -231,18 +231,6 @@
                       targetSdk="29">
         <new-permission name="android.permission.ACCESS_MEDIA_LOCATION" />
     </split-permission>
-    <split-permission name="android.permission.READ_EXTERNAL_STORAGE"
-                      targetSdk="33">
-        <new-permission name="android.permission.READ_MEDIA_AUDIO" />
-    </split-permission>
-    <split-permission name="android.permission.READ_EXTERNAL_STORAGE"
-                      targetSdk="33">
-        <new-permission name="android.permission.READ_MEDIA_VIDEO" />
-    </split-permission>
-    <split-permission name="android.permission.READ_EXTERNAL_STORAGE"
-                      targetSdk="33">
-        <new-permission name="android.permission.READ_MEDIA_IMAGE" />
-    </split-permission>
     <split-permission name="android.permission.BLUETOOTH"
                       targetSdk="31">
         <new-permission name="android.permission.BLUETOOTH_SCAN" />
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
index e2bc360..9384e2b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
@@ -245,7 +245,8 @@
         }
     }
 
-    private void onKeepClearAreasChanged(int displayId, List<Rect> keepClearAreas) {
+    private void onKeepClearAreasChanged(int displayId, List<Rect> restricted,
+            List<Rect> unrestricted) {
         synchronized (mDisplays) {
             if (mDisplays.get(displayId) == null || getDisplay(displayId) == null) {
                 Slog.w(TAG, "Skipping onKeepClearAreasChanged on unknown"
@@ -253,7 +254,8 @@
                 return;
             }
             for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) {
-                mDisplayChangedListeners.get(i).onKeepClearAreasChanged(displayId, keepClearAreas);
+                mDisplayChangedListeners.get(i)
+                    .onKeepClearAreasChanged(displayId, restricted, unrestricted);
             }
         }
     }
@@ -318,9 +320,10 @@
         }
 
         @Override
-        public void onKeepClearAreasChanged(int displayId, List<Rect> keepClearAreas) {
+        public void onKeepClearAreasChanged(int displayId, List<Rect> restricted,
+                List<Rect> unrestricted) {
             mMainExecutor.execute(() -> {
-                DisplayController.this.onKeepClearAreasChanged(displayId, keepClearAreas);
+                DisplayController.this.onKeepClearAreasChanged(displayId, restricted, unrestricted);
             });
         }
     }
@@ -361,6 +364,7 @@
         /**
          * Called when keep-clear areas on a display have changed.
          */
-        default void onKeepClearAreasChanged(int displayId, List<Rect> keepClearAreas) {}
+        default void onKeepClearAreasChanged(int displayId, List<Rect> restricted,
+                List<Rect> unrestricted) {}
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index f61e624..9f4ff7c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -471,9 +471,10 @@
     static Transitions provideTransitions(ShellTaskOrganizer organizer, TransactionPool pool,
             DisplayController displayController, Context context,
             @ShellMainThread ShellExecutor mainExecutor,
+            @ShellMainThread Handler mainHandler,
             @ShellAnimationThread ShellExecutor animExecutor) {
         return new Transitions(organizer, pool, displayController, context, mainExecutor,
-                animExecutor);
+                mainHandler, animExecutor);
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index d44db49..7307ba3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -102,7 +102,7 @@
                 MATCH_PARENT));
         ((LayoutParams) mDropZoneView1.getLayoutParams()).weight = 1;
         ((LayoutParams) mDropZoneView2.getLayoutParams()).weight = 1;
-        updateContainerMargins();
+        updateContainerMargins(getResources().getConfiguration().orientation);
     }
 
     @Override
@@ -127,20 +127,18 @@
     }
 
     public void onConfigChanged(Configuration newConfig) {
-        final int orientation = getResources().getConfiguration().orientation;
-        if (orientation == Configuration.ORIENTATION_LANDSCAPE
+        if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE
                 && getOrientation() != HORIZONTAL) {
             setOrientation(LinearLayout.HORIZONTAL);
-            updateContainerMargins();
-        } else if (orientation == Configuration.ORIENTATION_PORTRAIT
+            updateContainerMargins(newConfig.orientation);
+        } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT
                 && getOrientation() != VERTICAL) {
             setOrientation(LinearLayout.VERTICAL);
-            updateContainerMargins();
+            updateContainerMargins(newConfig.orientation);
         }
     }
 
-    private void updateContainerMargins() {
-        final int orientation = getResources().getConfiguration().orientation;
+    private void updateContainerMargins(int orientation) {
         final float halfMargin = mDisplayMargin / 2f;
         if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
             mDropZoneView1.setContainerMargin(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index e592101..a2c2f59 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -879,6 +879,7 @@
         if (mMainUnfoldController != null && mSideUnfoldController != null) {
             mMainUnfoldController.onSplitVisibilityChanged(mDividerVisible);
             mSideUnfoldController.onSplitVisibilityChanged(mDividerVisible);
+            updateUnfoldBounds();
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 79c8a87..27b6dc5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -25,6 +25,11 @@
 import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
 import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
+import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE_DRAWABLE;
+import static android.app.admin.DevicePolicyResources.Drawables.Source.PROFILE_SWITCH_ANIMATION;
+import static android.app.admin.DevicePolicyResources.Drawables.Style.OUTLINE;
+import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT;
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
@@ -57,11 +62,17 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityThread;
+import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.graphics.Color;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.hardware.HardwareBuffer;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.SystemProperties;
 import android.os.UserHandle;
@@ -118,6 +129,7 @@
     private final ShellExecutor mMainExecutor;
     private final ShellExecutor mAnimExecutor;
     private final TransitionAnimation mTransitionAnimation;
+    private final DevicePolicyManager mDevicePolicyManager;
 
     private final SurfaceSession mSurfaceSession = new SurfaceSession();
 
@@ -132,9 +144,24 @@
 
     private ScreenRotationAnimation mRotationAnimation;
 
+    private Drawable mEnterpriseThumbnailDrawable;
+
+    private BroadcastReceiver mEnterpriseResourceUpdatedReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            boolean isDrawable = intent.getBooleanExtra(
+                    EXTRA_RESOURCE_TYPE_DRAWABLE, /* default= */ false);
+            if (!isDrawable) {
+                return;
+            }
+            updateEnterpriseThumbnailDrawable();
+        }
+    };
+
     DefaultTransitionHandler(@NonNull DisplayController displayController,
             @NonNull TransactionPool transactionPool, Context context,
-            @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
+            @NonNull ShellExecutor mainExecutor, @NonNull Handler mainHandler,
+            @NonNull ShellExecutor animExecutor) {
         mDisplayController = displayController;
         mTransactionPool = transactionPool;
         mContext = context;
@@ -143,9 +170,23 @@
         mTransitionAnimation = new TransitionAnimation(context, false /* debug */, Transitions.TAG);
         mCurrentUserId = UserHandle.myUserId();
 
+        mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
+        updateEnterpriseThumbnailDrawable();
+        mContext.registerReceiver(
+                mEnterpriseResourceUpdatedReceiver,
+                new IntentFilter(ACTION_DEVICE_POLICY_RESOURCE_UPDATED),
+                /* broadcastPermission = */ null,
+                mainHandler);
+
         AttributeCache.init(context);
     }
 
+    private void updateEnterpriseThumbnailDrawable() {
+        mEnterpriseThumbnailDrawable = mDevicePolicyManager.getDrawable(
+                WORK_PROFILE_ICON, OUTLINE, PROFILE_SWITCH_ANIMATION,
+                () -> mContext.getDrawable(R.drawable.ic_corp_badge));
+    }
+
     @VisibleForTesting
     static boolean isRotationSeamless(@NonNull TransitionInfo info,
             DisplayController displayController) {
@@ -632,7 +673,7 @@
         final boolean isClose = Transitions.isClosingType(change.getMode());
         if (isOpen) {
             if (options.getType() == ANIM_OPEN_CROSS_PROFILE_APPS && isTask) {
-                attachCrossProfileThunmbnailAnimation(animations, finishCallback, change,
+                attachCrossProfileThumbnailAnimation(animations, finishCallback, change,
                         cornerRadius);
             } else if (options.getType() == ANIM_THUMBNAIL_SCALE_UP) {
                 attachThumbnailAnimation(animations, finishCallback, change, options, cornerRadius);
@@ -642,13 +683,14 @@
         }
     }
 
-    private void attachCrossProfileThunmbnailAnimation(@NonNull ArrayList<Animator> animations,
+    private void attachCrossProfileThumbnailAnimation(@NonNull ArrayList<Animator> animations,
             @NonNull Runnable finishCallback, TransitionInfo.Change change, float cornerRadius) {
-        final int thumbnailDrawableRes = change.getTaskInfo().userId == mCurrentUserId
-                ? R.drawable.ic_account_circle : R.drawable.ic_corp_badge;
         final Rect bounds = change.getEndAbsBounds();
+        // Show the right drawable depending on the user we're transitioning to.
+        final Drawable thumbnailDrawable = change.getTaskInfo().userId == mCurrentUserId
+                ? mContext.getDrawable(R.drawable.ic_account_circle) : mEnterpriseThumbnailDrawable;
         final HardwareBuffer thumbnail = mTransitionAnimation.createCrossProfileAppsThumbnail(
-                thumbnailDrawableRes, bounds);
+                thumbnailDrawable, bounds);
         if (thumbnail == null) {
             return;
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 33a98b2..86b73fc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -33,6 +33,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.ContentObserver;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.SystemProperties;
@@ -123,7 +124,8 @@
 
     public Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool,
             @NonNull DisplayController displayController, @NonNull Context context,
-            @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
+            @NonNull ShellExecutor mainExecutor, @NonNull Handler mainHandler,
+            @NonNull ShellExecutor animExecutor) {
         mOrganizer = organizer;
         mContext = context;
         mMainExecutor = mainExecutor;
@@ -132,7 +134,7 @@
         mPlayerImpl = new TransitionPlayerImpl();
         // The very last handler (0 in the list) should be the default one.
         mHandlers.add(new DefaultTransitionHandler(displayController, pool, context, mainExecutor,
-                animExecutor));
+                mainHandler, animExecutor));
         // Next lowest priority is remote transitions.
         mRemoteTransitionHandler = new RemoteTransitionHandler(mainExecutor);
         mHandlers.add(mRemoteTransitionHandler);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index 825320b..a6caefe 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -363,6 +363,45 @@
     }
 
     @Test
+    public void testOnEligibleForLetterboxEducationActivityChanged() {
+        final RunningTaskInfo taskInfo1 = createTaskInfo(12, WINDOWING_MODE_FULLSCREEN);
+        taskInfo1.displayId = DEFAULT_DISPLAY;
+        taskInfo1.topActivityEligibleForLetterboxEducation = false;
+        final TrackingTaskListener taskListener = new TrackingTaskListener();
+        mOrganizer.addListenerForType(taskListener, TASK_LISTENER_TYPE_FULLSCREEN);
+        mOrganizer.onTaskAppeared(taskInfo1, null);
+
+        // Task listener sent to compat UI is null if top activity isn't eligible for letterbox
+        // education.
+        verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
+
+        // Task listener is non-null if top activity is eligible for letterbox education and task
+        // is visible.
+        clearInvocations(mCompatUI);
+        final RunningTaskInfo taskInfo2 =
+                createTaskInfo(taskInfo1.taskId, WINDOWING_MODE_FULLSCREEN);
+        taskInfo2.displayId = taskInfo1.displayId;
+        taskInfo2.topActivityEligibleForLetterboxEducation = true;
+        taskInfo2.isVisible = true;
+        mOrganizer.onTaskInfoChanged(taskInfo2);
+        verify(mCompatUI).onCompatInfoChanged(taskInfo2, taskListener);
+
+        // Task listener is null if task is invisible.
+        clearInvocations(mCompatUI);
+        final RunningTaskInfo taskInfo3 =
+                createTaskInfo(taskInfo1.taskId, WINDOWING_MODE_FULLSCREEN);
+        taskInfo3.displayId = taskInfo1.displayId;
+        taskInfo3.topActivityEligibleForLetterboxEducation = true;
+        taskInfo3.isVisible = false;
+        mOrganizer.onTaskInfoChanged(taskInfo3);
+        verify(mCompatUI).onCompatInfoChanged(taskInfo3, null /* taskListener */);
+
+        clearInvocations(mCompatUI);
+        mOrganizer.onTaskVanished(taskInfo1);
+        verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
+    }
+
+    @Test
     public void testOnCameraCompatActivityChanged() {
         final RunningTaskInfo taskInfo1 = createTaskInfo(1, WINDOWING_MODE_FULLSCREEN);
         taskInfo1.displayId = DEFAULT_DISPLAY;
@@ -375,7 +414,7 @@
         // compat control.
         verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
 
-        // Task linster is non-null when request a camera compat control for a visible task.
+        // Task listener is non-null when request a camera compat control for a visible task.
         clearInvocations(mCompatUI);
         final RunningTaskInfo taskInfo2 =
                 createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index e391713..0f4a06f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -54,7 +54,9 @@
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
 import android.os.Binder;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.view.IDisplayWindowListener;
 import android.view.IWindowManager;
@@ -84,8 +86,6 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
 
 import java.util.ArrayList;
 
@@ -106,6 +106,7 @@
     private final TestShellExecutor mMainExecutor = new TestShellExecutor();
     private final ShellExecutor mAnimExecutor = new TestShellExecutor();
     private final TestTransitionHandler mDefaultHandler = new TestTransitionHandler();
+    private final Handler mMainHandler = new Handler(Looper.getMainLooper());
 
     @Before
     public void setUp() {
@@ -752,7 +753,7 @@
 
     private Transitions createTestTransitions() {
         return new Transitions(mOrganizer, mTransactionPool, createTestDisplayController(),
-                mContext, mMainExecutor, mAnimExecutor);
+                mContext, mMainExecutor, mMainHandler, mAnimExecutor);
     }
 //
 //    private class TestDisplayController extends DisplayController {
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 1b4c0bc..15a398d 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -562,6 +562,7 @@
      * Indicates the volume set/adjust call is for Bluetooth absolute volume
      * @hide
      */
+    @SystemApi
     public static final int FLAG_BLUETOOTH_ABS_VOLUME = 1 << 6;
 
     /**
diff --git a/media/java/android/media/BtProfileConnectionInfo.java b/media/java/android/media/BtProfileConnectionInfo.java
index d1bb41e..86dc6e0 100644
--- a/media/java/android/media/BtProfileConnectionInfo.java
+++ b/media/java/android/media/BtProfileConnectionInfo.java
@@ -15,39 +15,24 @@
  */
 package android.media;
 
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.bluetooth.BluetoothProfile;
 import android.os.Parcel;
 import android.os.Parcelable;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
 /**
  * Contains information about Bluetooth profile connection state changed
  * {@hide}
  */
 @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
 public final class BtProfileConnectionInfo implements Parcelable {
-    /** @hide */
-    @IntDef({
-            BluetoothProfile.A2DP,
-            BluetoothProfile.A2DP_SINK,
-            BluetoothProfile.HEADSET, // Can only be set by BtHelper
-            BluetoothProfile.HEARING_AID,
-            BluetoothProfile.LE_AUDIO,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface BtProfile {}
-
-    private final @BtProfile int mProfile;
+    private final int mProfile;
     private final boolean mSupprNoisy;
     private final int mVolume;
     private final boolean mIsLeOutput;
 
-    private BtProfileConnectionInfo(@BtProfile int profile, boolean suppressNoisyIntent, int volume,
+    private BtProfileConnectionInfo(int profile, boolean suppressNoisyIntent, int volume,
             boolean isLeOutput) {
         mProfile = profile;
         mSupprNoisy = suppressNoisyIntent;
@@ -59,7 +44,7 @@
      * Constructor used by BtHelper when a profile is connected
      * {@hide}
      */
-    public BtProfileConnectionInfo(@BtProfile int profile) {
+    public BtProfileConnectionInfo(int profile) {
         this(profile, false, -1, false);
     }
 
@@ -142,7 +127,7 @@
     /**
      * @return The profile connection
      */
-    public @BtProfile int getProfile() {
+    public int getProfile() {
         return mProfile;
     }
 
diff --git a/media/java/android/media/IMediaRouter2Manager.aidl b/media/java/android/media/IMediaRouter2Manager.aidl
index 5113dc2..71dc2a7 100644
--- a/media/java/android/media/IMediaRouter2Manager.aidl
+++ b/media/java/android/media/IMediaRouter2Manager.aidl
@@ -18,6 +18,7 @@
 
 import android.media.MediaRoute2ProviderInfo;
 import android.media.MediaRoute2Info;
+import android.media.RouteDiscoveryPreference;
 import android.media.RoutingSessionInfo;
 
 /**
@@ -27,7 +28,8 @@
     void notifySessionCreated(int requestId, in RoutingSessionInfo session);
     void notifySessionUpdated(in RoutingSessionInfo session);
     void notifySessionReleased(in RoutingSessionInfo session);
-    void notifyPreferredFeaturesChanged(String packageName, in List<String> preferredFeatures);
+    void notifyDiscoveryPreferenceChanged(String packageName,
+            in RouteDiscoveryPreference discoveryPreference);
     void notifyRoutesAdded(in List<MediaRoute2Info> routes);
     void notifyRoutesRemoved(in List<MediaRoute2Info> routes);
     void notifyRoutesChanged(in List<MediaRoute2Info> routes);
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 2427fa6..ee0293d 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -34,6 +34,7 @@
 import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * Describes the properties of a route.
@@ -340,10 +341,12 @@
     @ConnectionState
     final int mConnectionState;
     final String mClientPackageName;
+    final String mPackageName;
     final int mVolumeHandling;
     final int mVolumeMax;
     final int mVolume;
     final String mAddress;
+    final Set<String> mDeduplicationIds;
     final Bundle mExtras;
     final String mProviderId;
 
@@ -357,10 +360,12 @@
         mDescription = builder.mDescription;
         mConnectionState = builder.mConnectionState;
         mClientPackageName = builder.mClientPackageName;
+        mPackageName = builder.mPackageName;
         mVolumeHandling = builder.mVolumeHandling;
         mVolumeMax = builder.mVolumeMax;
         mVolume = builder.mVolume;
         mAddress = builder.mAddress;
+        mDeduplicationIds = builder.mDeduplicationIds;
         mExtras = builder.mExtras;
         mProviderId = builder.mProviderId;
     }
@@ -375,10 +380,12 @@
         mDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
         mConnectionState = in.readInt();
         mClientPackageName = in.readString();
+        mPackageName = in.readString();
         mVolumeHandling = in.readInt();
         mVolumeMax = in.readInt();
         mVolume = in.readInt();
         mAddress = in.readString();
+        mDeduplicationIds = Set.of(in.readStringArray());
         mExtras = in.readBundle();
         mProviderId = in.readString();
     }
@@ -486,6 +493,17 @@
     }
 
     /**
+     * Gets the package name of the provider that published the route.
+     * <p>
+     * It is set by the system service.
+     * @hide
+     */
+    @Nullable
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    /**
      * Gets information about how volume is handled on the route.
      *
      * @return {@link #PLAYBACK_VOLUME_FIXED} or {@link #PLAYBACK_VOLUME_VARIABLE}
@@ -518,6 +536,18 @@
         return mAddress;
     }
 
+    /**
+     * Gets the Deduplication ID of the route if available.
+     * @see RouteDiscoveryPreference#shouldRemoveDuplicates()
+     */
+    @NonNull
+    public Set<String> getDeduplicationIds() {
+        return mDeduplicationIds;
+    }
+
+    /**
+     * Gets an optional bundle with extra data.
+     */
     @Nullable
     public Bundle getExtras() {
         return mExtras == null ? null : new Bundle(mExtras);
@@ -549,7 +579,7 @@
      * Returns if the route has at least one of the specified route features.
      *
      * @param features the list of route features to consider
-     * @return true if the route has at least one feature in the list
+     * @return {@code true} if the route has at least one feature in the list
      * @hide
      */
     public boolean hasAnyFeatures(@NonNull Collection<String> features) {
@@ -563,6 +593,21 @@
     }
 
     /**
+     * Returns if the route has all the specified route features.
+     *
+     * @hide
+     */
+    public boolean hasAllFeatures(@NonNull Collection<String> features) {
+        Objects.requireNonNull(features, "features must not be null");
+        for (String feature : features) {
+            if (!getFeatures().contains(feature)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
      * Returns true if the route info has all of the required field.
      * A route is valid if and only if it is obtained from
      * {@link com.android.server.media.MediaRouterService}.
@@ -596,10 +641,12 @@
                 && Objects.equals(mDescription, other.mDescription)
                 && (mConnectionState == other.mConnectionState)
                 && Objects.equals(mClientPackageName, other.mClientPackageName)
+                && Objects.equals(mPackageName, other.mPackageName)
                 && (mVolumeHandling == other.mVolumeHandling)
                 && (mVolumeMax == other.mVolumeMax)
                 && (mVolume == other.mVolume)
                 && Objects.equals(mAddress, other.mAddress)
+                && Objects.equals(mDeduplicationIds, other.mDeduplicationIds)
                 && Objects.equals(mProviderId, other.mProviderId);
     }
 
@@ -607,8 +654,8 @@
     public int hashCode() {
         // Note: mExtras is not included.
         return Objects.hash(mId, mName, mFeatures, mType, mIsSystem, mIconUri, mDescription,
-                mConnectionState, mClientPackageName, mVolumeHandling, mVolumeMax, mVolume,
-                mAddress, mProviderId);
+                mConnectionState, mClientPackageName, mPackageName, mVolumeHandling, mVolumeMax,
+                mVolume, mAddress, mDeduplicationIds, mProviderId);
     }
 
     @Override
@@ -626,6 +673,7 @@
                 .append(", volumeHandling=").append(getVolumeHandling())
                 .append(", volumeMax=").append(getVolumeMax())
                 .append(", volume=").append(getVolume())
+                .append(", deduplicationIds=").append(String.join(",", getDeduplicationIds()))
                 .append(", providerId=").append(getProviderId())
                 .append(" }");
         return result.toString();
@@ -647,10 +695,12 @@
         TextUtils.writeToParcel(mDescription, dest, flags);
         dest.writeInt(mConnectionState);
         dest.writeString(mClientPackageName);
+        dest.writeString(mPackageName);
         dest.writeInt(mVolumeHandling);
         dest.writeInt(mVolumeMax);
         dest.writeInt(mVolume);
         dest.writeString(mAddress);
+        dest.writeStringArray(mDeduplicationIds.toArray(new String[mDeduplicationIds.size()]));
         dest.writeBundle(mExtras);
         dest.writeString(mProviderId);
     }
@@ -671,10 +721,12 @@
         @ConnectionState
         int mConnectionState;
         String mClientPackageName;
+        String mPackageName;
         int mVolumeHandling = PLAYBACK_VOLUME_FIXED;
         int mVolumeMax;
         int mVolume;
         String mAddress;
+        Set<String> mDeduplicationIds;
         Bundle mExtras;
         String mProviderId;
 
@@ -698,6 +750,7 @@
             mId = id;
             mName = name;
             mFeatures = new ArrayList<>();
+            mDeduplicationIds = Set.of();
         }
 
         /**
@@ -733,10 +786,12 @@
             mDescription = routeInfo.mDescription;
             mConnectionState = routeInfo.mConnectionState;
             mClientPackageName = routeInfo.mClientPackageName;
+            mPackageName = routeInfo.mPackageName;
             mVolumeHandling = routeInfo.mVolumeHandling;
             mVolumeMax = routeInfo.mVolumeMax;
             mVolume = routeInfo.mVolume;
             mAddress = routeInfo.mAddress;
+            mDeduplicationIds = Set.copyOf(routeInfo.mDeduplicationIds);
             if (routeInfo.mExtras != null) {
                 mExtras = new Bundle(routeInfo.mExtras);
             }
@@ -860,6 +915,16 @@
         }
 
         /**
+         * Sets the package name of the route.
+         * @hide
+         */
+        @NonNull
+        public Builder setPackageName(@NonNull String packageName) {
+            mPackageName = packageName;
+            return this;
+        }
+
+        /**
          * Sets the route's volume handling.
          */
         @NonNull
@@ -897,6 +962,20 @@
         }
 
         /**
+         * Sets the deduplication ID of the route.
+         * Routes have the same ID could be removed even when
+         * they are from different providers.
+         * <p>
+         * If it's {@code null}, the route will not be removed.
+         * @see RouteDiscoveryPreference#shouldRemoveDuplicates()
+         */
+        @NonNull
+        public Builder setDeduplicationIds(@NonNull Set<String> id) {
+            mDeduplicationIds = Set.copyOf(id);
+            return this;
+        }
+
+        /**
          * Sets a bundle of extras for the route.
          * <p>
          * Note: The extras will not affect the result of {@link MediaRoute2Info#equals(Object)}.
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 4b32dbf..b485eb5 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -34,15 +34,18 @@
 import android.os.ServiceManager;
 import android.text.TextUtils;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -302,8 +305,7 @@
         mSystemController = new SystemRoutingController(
                 ensureClientPackageNameForSystemSession(
                         sManager.getSystemRoutingSession(clientPackageName)));
-        mDiscoveryPreference = new RouteDiscoveryPreference.Builder(
-                sManager.getPreferredFeatures(clientPackageName), true).build();
+        mDiscoveryPreference = sManager.getDiscoveryPreference(clientPackageName);
         updateAllRoutesFromManager();
 
         // Only used by non-system MediaRouter2.
@@ -1060,11 +1062,48 @@
                 .build();
     }
 
+    private List<MediaRoute2Info> getSortedRoutes(List<MediaRoute2Info> routes,
+            RouteDiscoveryPreference preference) {
+        if (!preference.shouldRemoveDuplicates()) {
+            return routes;
+        }
+        Map<String, Integer> packagePriority = new ArrayMap<>();
+        int count = preference.getDeduplicationPackageOrder().size();
+        for (int i = 0; i < count; i++) {
+            // the last package will have 1 as the priority
+            packagePriority.put(preference.getDeduplicationPackageOrder().get(i), count - i);
+        }
+        ArrayList<MediaRoute2Info> sortedRoutes = new ArrayList<>(routes);
+        // take the negative for descending order
+        sortedRoutes.sort(Comparator.comparingInt(
+                r -> -packagePriority.getOrDefault(r.getPackageName(), 0)));
+        return sortedRoutes;
+    }
+
     private List<MediaRoute2Info> filterRoutes(List<MediaRoute2Info> routes,
-            RouteDiscoveryPreference discoveryRequest) {
-        return routes.stream()
-                .filter(route -> route.hasAnyFeatures(discoveryRequest.getPreferredFeatures()))
-                .collect(Collectors.toList());
+            RouteDiscoveryPreference discoveryPreference) {
+
+        Set<String> deduplicationIdSet = new ArraySet<>();
+
+        List<MediaRoute2Info> filteredRoutes = new ArrayList<>();
+        for (MediaRoute2Info route : getSortedRoutes(routes, discoveryPreference)) {
+            if (!route.hasAllFeatures(discoveryPreference.getRequiredFeatures())
+                    || !route.hasAnyFeatures(discoveryPreference.getPreferredFeatures())) {
+                continue;
+            }
+            if (!discoveryPreference.getAllowedPackages().isEmpty()
+                    && !discoveryPreference.getAllowedPackages().contains(route.getPackageName())) {
+                continue;
+            }
+            if (discoveryPreference.shouldRemoveDuplicates()) {
+                if (Collections.disjoint(deduplicationIdSet, route.getDeduplicationIds())) {
+                    continue;
+                }
+                deduplicationIdSet.addAll(route.getDeduplicationIds());
+            }
+            filteredRoutes.add(route);
+        }
+        return filteredRoutes;
     }
 
     private void updateAllRoutesFromManager() {
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 83fa7c2..8635c0e 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -29,6 +29,8 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
@@ -36,15 +38,18 @@
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
 /**
@@ -84,7 +89,8 @@
     @GuardedBy("mRoutesLock")
     private final Map<String, MediaRoute2Info> mRoutes = new HashMap<>();
     @NonNull
-    final ConcurrentMap<String, List<String>> mPreferredFeaturesMap = new ConcurrentHashMap<>();
+    final ConcurrentMap<String, RouteDiscoveryPreference> mDiscoveryPreferenceMap =
+            new ConcurrentHashMap<>();
 
     private final AtomicInteger mNextRequestId = new AtomicInteger(1);
     private final CopyOnWriteArrayList<TransferRequest> mTransferRequests =
@@ -247,25 +253,8 @@
      */
     @NonNull
     public List<MediaRoute2Info> getAvailableRoutes(@NonNull RoutingSessionInfo sessionInfo) {
-        Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
-
-        List<MediaRoute2Info> routes = new ArrayList<>();
-
-        String packageName = sessionInfo.getClientPackageName();
-        List<String> preferredFeatures = mPreferredFeaturesMap.get(packageName);
-        if (preferredFeatures == null) {
-            preferredFeatures = Collections.emptyList();
-        }
-        synchronized (mRoutesLock) {
-            for (MediaRoute2Info route : mRoutes.values()) {
-                if (route.hasAnyFeatures(preferredFeatures)
-                        || sessionInfo.getSelectedRoutes().contains(route.getId())
-                        || sessionInfo.getTransferableRoutes().contains(route.getId())) {
-                    routes.add(route);
-                }
-            }
-        }
-        return routes;
+        return getFilteredRoutes(sessionInfo, /*includeSelectedRoutes=*/true,
+                null);
     }
 
     /**
@@ -281,27 +270,70 @@
      */
     @NonNull
     public List<MediaRoute2Info> getTransferableRoutes(@NonNull RoutingSessionInfo sessionInfo) {
+        return getFilteredRoutes(sessionInfo, /*includeSelectedRoutes=*/false,
+                (route) -> sessionInfo.isSystemSession() ^ route.isSystemRoute());
+    }
+
+    private List<MediaRoute2Info> getSortedRoutes(RouteDiscoveryPreference preference) {
+        if (!preference.shouldRemoveDuplicates()) {
+            synchronized (mRoutesLock) {
+                return List.copyOf(mRoutes.values());
+            }
+        }
+        Map<String, Integer> packagePriority = new ArrayMap<>();
+        int count = preference.getDeduplicationPackageOrder().size();
+        for (int i = 0; i < count; i++) {
+            // the last package will have 1 as the priority
+            packagePriority.put(preference.getDeduplicationPackageOrder().get(i), count - i);
+        }
+        ArrayList<MediaRoute2Info> routes;
+        synchronized (mRoutesLock) {
+            routes = new ArrayList<>(mRoutes.values());
+        }
+        // take the negative for descending order
+        routes.sort(Comparator.comparingInt(
+                r -> -packagePriority.getOrDefault(r.getPackageName(), 0)));
+        return routes;
+    }
+
+    private List<MediaRoute2Info> getFilteredRoutes(@NonNull RoutingSessionInfo sessionInfo,
+            boolean includeSelectedRoutes,
+            @Nullable Predicate<MediaRoute2Info> additionalFilter) {
         Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
 
         List<MediaRoute2Info> routes = new ArrayList<>();
 
+        Set<String> deduplicationIdSet = new ArraySet<>();
         String packageName = sessionInfo.getClientPackageName();
-        List<String> preferredFeatures = mPreferredFeaturesMap.get(packageName);
-        if (preferredFeatures == null) {
-            preferredFeatures = Collections.emptyList();
-        }
-        synchronized (mRoutesLock) {
-            for (MediaRoute2Info route : mRoutes.values()) {
-                if (sessionInfo.getTransferableRoutes().contains(route.getId())) {
-                    routes.add(route);
+        RouteDiscoveryPreference discoveryPreference =
+                mDiscoveryPreferenceMap.getOrDefault(packageName, RouteDiscoveryPreference.EMPTY);
+
+        for (MediaRoute2Info route : getSortedRoutes(discoveryPreference)) {
+            if (sessionInfo.getTransferableRoutes().contains(route.getId())
+                    || (includeSelectedRoutes
+                    && sessionInfo.getSelectedRoutes().contains(route.getId()))) {
+                routes.add(route);
+                continue;
+            }
+            if (!route.hasAllFeatures(discoveryPreference.getRequiredFeatures())
+                    || !route.hasAnyFeatures(discoveryPreference.getPreferredFeatures())) {
+                continue;
+            }
+            if (!discoveryPreference.getAllowedPackages().isEmpty()
+                    && !discoveryPreference.getAllowedPackages()
+                    .contains(route.getPackageName())) {
+                continue;
+            }
+            if (additionalFilter != null && !additionalFilter.test(route)) {
+                continue;
+            }
+            if (discoveryPreference.shouldRemoveDuplicates()) {
+                if (Collections.disjoint(deduplicationIdSet, route.getDeduplicationIds())) {
                     continue;
                 }
-                // Add Phone -> Cast and Cast -> Phone
-                if (route.hasAnyFeatures(preferredFeatures)
-                        && (sessionInfo.isSystemSession() ^ route.isSystemRoute())) {
-                    routes.add(route);
-                }
+                deduplicationIdSet.addAll(route.getDeduplicationIds());
             }
+            routes.add(route);
         }
         return routes;
     }
@@ -310,44 +342,10 @@
      * Returns the preferred features of the specified package name.
      */
     @NonNull
-    public List<String> getPreferredFeatures(@NonNull String packageName) {
+    public RouteDiscoveryPreference getDiscoveryPreference(@NonNull String packageName) {
         Objects.requireNonNull(packageName, "packageName must not be null");
 
-        List<String> preferredFeatures = mPreferredFeaturesMap.get(packageName);
-        if (preferredFeatures == null) {
-            preferredFeatures = Collections.emptyList();
-        }
-        return preferredFeatures;
-    }
-
-    /**
-     * Returns a list of routes which are related to the given package name in the given route list.
-     */
-    @NonNull
-    public List<MediaRoute2Info> filterRoutesForPackage(@NonNull List<MediaRoute2Info> routes,
-            @NonNull String packageName) {
-        Objects.requireNonNull(routes, "routes must not be null");
-        Objects.requireNonNull(packageName, "packageName must not be null");
-
-        List<RoutingSessionInfo> sessions = getRoutingSessions(packageName);
-        RoutingSessionInfo sessionInfo = sessions.get(sessions.size() - 1);
-
-        List<MediaRoute2Info> result = new ArrayList<>();
-        List<String> preferredFeatures = mPreferredFeaturesMap.get(packageName);
-        if (preferredFeatures == null) {
-            preferredFeatures = Collections.emptyList();
-        }
-
-        synchronized (mRoutesLock) {
-            for (MediaRoute2Info route : routes) {
-                if (route.hasAnyFeatures(preferredFeatures)
-                        || sessionInfo.getSelectedRoutes().contains(route.getId())
-                        || sessionInfo.getTransferableRoutes().contains(route.getId())) {
-                    result.add(route);
-                }
-            }
-        }
-        return result;
+        return mDiscoveryPreferenceMap.getOrDefault(packageName, RouteDiscoveryPreference.EMPTY);
     }
 
     /**
@@ -713,19 +711,19 @@
         }
     }
 
-    void updatePreferredFeatures(String packageName, List<String> preferredFeatures) {
-        if (preferredFeatures == null) {
-            mPreferredFeaturesMap.remove(packageName);
+    void updateDiscoveryPreference(String packageName, RouteDiscoveryPreference preference) {
+        if (preference == null) {
+            mDiscoveryPreferenceMap.remove(packageName);
             return;
         }
-        List<String> prevFeatures = mPreferredFeaturesMap.put(packageName, preferredFeatures);
-        if ((prevFeatures == null && preferredFeatures.size() == 0)
-                || Objects.equals(preferredFeatures, prevFeatures)) {
+        RouteDiscoveryPreference prevPreference =
+                mDiscoveryPreferenceMap.put(packageName, preference);
+        if (Objects.equals(preference, prevPreference)) {
             return;
         }
         for (CallbackRecord record : mCallbackRecords) {
             record.mExecutor.execute(() -> record.mCallback
-                    .onPreferredFeaturesChanged(packageName, preferredFeatures));
+                    .onDiscoveryPreferenceChanged(packageName, preference));
         }
     }
 
@@ -1047,6 +1045,17 @@
                 @NonNull List<String> preferredFeatures) {}
 
         /**
+         * Called when the preferred route features of an app is changed.
+         *
+         * @param packageName the package name of the application
+         * @param discoveryPreference the new discovery preference set by the application.
+         */
+        default void onDiscoveryPreferenceChanged(@NonNull String packageName,
+                @NonNull RouteDiscoveryPreference discoveryPreference) {
+            onPreferredFeaturesChanged(packageName, discoveryPreference.getPreferredFeatures());
+        }
+
+        /**
          * Called when a previous request has failed.
          *
          * @param reason the reason that the request has failed. Can be one of followings:
@@ -1125,9 +1134,10 @@
         }
 
         @Override
-        public void notifyPreferredFeaturesChanged(String packageName, List<String> features) {
-            mHandler.sendMessage(obtainMessage(MediaRouter2Manager::updatePreferredFeatures,
-                    MediaRouter2Manager.this, packageName, features));
+        public void notifyDiscoveryPreferenceChanged(String packageName,
+                RouteDiscoveryPreference discoveryPreference) {
+            mHandler.sendMessage(obtainMessage(MediaRouter2Manager::updateDiscoveryPreference,
+                    MediaRouter2Manager.this, packageName, discoveryPreference));
         }
 
         @Override
diff --git a/media/java/android/media/RouteDiscoveryPreference.java b/media/java/android/media/RouteDiscoveryPreference.java
index 37fee84..0045018 100644
--- a/media/java/android/media/RouteDiscoveryPreference.java
+++ b/media/java/android/media/RouteDiscoveryPreference.java
@@ -64,6 +64,13 @@
 
     @NonNull
     private final List<String> mPreferredFeatures;
+    @NonNull
+    private final List<String> mRequiredFeatures;
+    @NonNull
+    private final List<String> mPackagesOrder;
+    @NonNull
+    private final List<String> mAllowedPackages;
+
     private final boolean mShouldPerformActiveScan;
     @Nullable
     private final Bundle mExtras;
@@ -78,12 +85,18 @@
 
     RouteDiscoveryPreference(@NonNull Builder builder) {
         mPreferredFeatures = builder.mPreferredFeatures;
+        mRequiredFeatures = builder.mRequiredFeatures;
+        mPackagesOrder = builder.mPackageOrder;
+        mAllowedPackages = builder.mAllowedPackages;
         mShouldPerformActiveScan = builder.mActiveScan;
         mExtras = builder.mExtras;
     }
 
     RouteDiscoveryPreference(@NonNull Parcel in) {
         mPreferredFeatures = in.createStringArrayList();
+        mRequiredFeatures = in.createStringArrayList();
+        mPackagesOrder = in.createStringArrayList();
+        mAllowedPackages = in.createStringArrayList();
         mShouldPerformActiveScan = in.readBoolean();
         mExtras = in.readBundle();
     }
@@ -96,6 +109,8 @@
      * {@link MediaRoute2Info#FEATURE_LIVE_AUDIO}, {@link MediaRoute2Info#FEATURE_LIVE_VIDEO},
      * or {@link MediaRoute2Info#FEATURE_REMOTE_PLAYBACK} or custom features defined by a provider.
      * </p>
+     *
+     * @see #getRequiredFeatures()
      */
     @NonNull
     public List<String> getPreferredFeatures() {
@@ -103,6 +118,47 @@
     }
 
     /**
+     * Gets the required features of routes that media router would like to discover.
+     * <p>
+     * Routes that have all the required features will be discovered.
+     * They may include predefined features such as
+     * {@link MediaRoute2Info#FEATURE_LIVE_AUDIO}, {@link MediaRoute2Info#FEATURE_LIVE_VIDEO},
+     * or {@link MediaRoute2Info#FEATURE_REMOTE_PLAYBACK} or custom features defined by a provider.
+     *
+     * @see #getPreferredFeatures()
+     */
+    @NonNull
+    public List<String> getRequiredFeatures() {
+        return mRequiredFeatures;
+    }
+
+    /**
+     * Gets the ordered list of package names used to remove duplicate routes.
+     * <p>
+     * Duplicate route removal is enabled if the returned list is non-empty. Routes are deduplicated
+     * based on their {@link MediaRoute2Info#getDeduplicationIds() deduplication IDs}. If two routes
+     * have a deduplication ID in common, only the route from the provider whose package name is
+     * first in the provided list will remain.
+     *
+     * @see #shouldRemoveDuplicates()
+     */
+    @NonNull
+    public List<String> getDeduplicationPackageOrder() {
+        return mPackagesOrder;
+    }
+
+    /**
+     * Gets the list of allowed packages.
+     * <p>
+     * If it's not empty, it will only discover routes from the provider whose package name
+     * belongs to the list.
+     */
+    @NonNull
+    public List<String> getAllowedPackages() {
+        return mAllowedPackages;
+    }
+
+    /**
      * Gets whether active scanning should be performed.
      * <p>
      * If any of discovery preferences sets this as {@code true}, active scanning will
@@ -114,6 +170,15 @@
     }
 
     /**
+     * Gets whether duplicate routes removal is enabled.
+     *
+     * @see #getDeduplicationPackageOrder()
+     */
+    public boolean shouldRemoveDuplicates() {
+        return !mPackagesOrder.isEmpty();
+    }
+
+    /**
      * @hide
      */
     public Bundle getExtras() {
@@ -128,6 +193,9 @@
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeStringList(mPreferredFeatures);
+        dest.writeStringList(mRequiredFeatures);
+        dest.writeStringList(mPackagesOrder);
+        dest.writeStringList(mAllowedPackages);
         dest.writeBoolean(mShouldPerformActiveScan);
         dest.writeBundle(mExtras);
     }
@@ -155,14 +223,17 @@
             return false;
         }
         RouteDiscoveryPreference other = (RouteDiscoveryPreference) o;
-        //TODO: Make this order-free
         return Objects.equals(mPreferredFeatures, other.mPreferredFeatures)
+                && Objects.equals(mRequiredFeatures, other.mRequiredFeatures)
+                && Objects.equals(mPackagesOrder, other.mPackagesOrder)
+                && Objects.equals(mAllowedPackages, other.mAllowedPackages)
                 && mShouldPerformActiveScan == other.mShouldPerformActiveScan;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mPreferredFeatures, mShouldPerformActiveScan);
+        return Objects.hash(mPreferredFeatures, mRequiredFeatures, mPackagesOrder, mAllowedPackages,
+                mShouldPerformActiveScan);
     }
 
     /**
@@ -170,13 +241,21 @@
      */
     public static final class Builder {
         List<String> mPreferredFeatures;
+        List<String> mRequiredFeatures;
+        List<String> mPackageOrder;
+        List<String> mAllowedPackages;
+
         boolean mActiveScan;
+
         Bundle mExtras;
 
         public Builder(@NonNull List<String> preferredFeatures, boolean activeScan) {
             Objects.requireNonNull(preferredFeatures, "preferredFeatures must not be null");
             mPreferredFeatures = preferredFeatures.stream().filter(str -> !TextUtils.isEmpty(str))
                     .collect(Collectors.toList());
+            mRequiredFeatures = List.of();
+            mPackageOrder = List.of();
+            mAllowedPackages = List.of();
             mActiveScan = activeScan;
         }
 
@@ -184,12 +263,15 @@
             Objects.requireNonNull(preference, "preference must not be null");
 
             mPreferredFeatures = preference.getPreferredFeatures();
+            mRequiredFeatures = preference.getRequiredFeatures();
+            mPackageOrder = preference.getDeduplicationPackageOrder();
+            mAllowedPackages = preference.getAllowedPackages();
             mActiveScan = preference.shouldPerformActiveScan();
             mExtras = preference.getExtras();
         }
 
         /**
-         * A constructor to combine all of the preferences into a single preference.
+         * A constructor to combine all the preferences into a single preference.
          * It ignores extras of preferences.
          *
          * @hide
@@ -224,6 +306,30 @@
         }
 
         /**
+         * Sets the required route features to discover.
+         */
+        @NonNull
+        public Builder setRequiredFeatures(@NonNull List<String> requiredFeatures) {
+            Objects.requireNonNull(requiredFeatures, "preferredFeatures must not be null");
+            mRequiredFeatures = requiredFeatures.stream().filter(str -> !TextUtils.isEmpty(str))
+                    .collect(Collectors.toList());
+            return this;
+        }
+
+        /**
+         * Sets the list of package names of providers that media router would like to discover.
+         * <p>
+         * If it's non-empty, media router only discovers route from the provider in the list.
+         * The default value is empty, which discovers routes from all providers.
+         */
+        @NonNull
+        public Builder setAllowedPackages(@NonNull List<String> allowedPackages) {
+            Objects.requireNonNull(allowedPackages, "allowedPackages must not be null");
+            mAllowedPackages = List.copyOf(allowedPackages);
+            return this;
+        }
+
+        /**
          * Sets if active scanning should be performed.
          * <p>
          * Since active scanning uses more system resources, set this as {@code true} only
@@ -237,6 +343,24 @@
         }
 
         /**
+         * Sets the order of packages to use when removing duplicate routes.
+         * <p>
+         * Routes are deduplicated based on their
+         * {@link MediaRoute2Info#getDeduplicationIds() deduplication IDs}.
+         * If two routes have a deduplication ID in common, only the route from the provider whose
+         * package name is first in the provided list will remain.
+         *
+         * @param packageOrder ordered list of package names used to remove duplicate routes, or an
+         *                     empty list if deduplication should not be enabled.
+         */
+        @NonNull
+        public Builder setDeduplicationPackageOrder(@NonNull List<String> packageOrder) {
+            Objects.requireNonNull(packageOrder, "packageOrder must not be null");
+            mPackageOrder = List.copyOf(packageOrder);
+            return this;
+        }
+
+        /**
          * Sets the extras of the route.
          * @hide
          */
diff --git a/packages/ConnectivityT/framework-t/Android.bp b/packages/ConnectivityT/framework-t/Android.bp
index 223bdcdd..327b1fb 100644
--- a/packages/ConnectivityT/framework-t/Android.bp
+++ b/packages/ConnectivityT/framework-t/Android.bp
@@ -39,7 +39,6 @@
         "src/android/net/TrafficStats.java",
         "src/android/net/UnderlyingNetworkInfo.*",
         "src/android/net/netstats/**/*.*",
-        "src/com/android/server/NetworkManagementSocketTagger.java",
     ],
     path: "src",
     visibility: [
@@ -176,3 +175,34 @@
         "//packages/modules/Connectivity:__subpackages__",
     ],
 }
+
+cc_library_shared {
+    name: "libframework-connectivity-tiramisu-jni",
+    min_sdk_version: "30",
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-unused-parameter",
+        // Don't warn about S API usage even with
+        // min_sdk 30: the library is only loaded
+        // on S+ devices
+        "-Wno-unguarded-availability",
+        "-Wthread-safety",
+    ],
+    srcs: [
+        "jni/android_net_TrafficStats.cpp",
+        "jni/onload.cpp",
+    ],
+    shared_libs: [
+        "liblog",
+    ],
+    static_libs: [
+        "libnativehelper_compat_libc++",
+    ],
+    stl: "none",
+    apex_available: [
+        "com.android.tethering",
+        // TODO: remove when ConnectivityT moves to APEX.
+        "//apex_available:platform",
+    ],
+}
diff --git a/packages/ConnectivityT/framework-t/jni/android_net_TrafficStats.cpp b/packages/ConnectivityT/framework-t/jni/android_net_TrafficStats.cpp
new file mode 100644
index 0000000..f3c58b1
--- /dev/null
+++ b/packages/ConnectivityT/framework-t/jni/android_net_TrafficStats.cpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 <android/file_descriptor_jni.h>
+#include <android/multinetwork.h>
+#include <nativehelper/JNIHelp.h>
+
+namespace android {
+
+static jint tagSocketFd(JNIEnv* env, jclass, jobject fileDescriptor, jint tag, jint uid) {
+  int fd = AFileDescriptor_getFd(env, fileDescriptor);
+  if (fd == -1) return -EBADF;
+  return android_tag_socket_with_uid(fd, tag, uid);
+}
+
+static jint untagSocketFd(JNIEnv* env, jclass, jobject fileDescriptor) {
+  int fd = AFileDescriptor_getFd(env, fileDescriptor);
+  if (fd == -1) return -EBADF;
+  return android_untag_socket(fd);
+}
+
+static const JNINativeMethod gMethods[] = {
+    /* name, signature, funcPtr */
+    { "native_tagSocketFd", "(Ljava/io/FileDescriptor;II)I", (void*) tagSocketFd },
+    { "native_untagSocketFd", "(Ljava/io/FileDescriptor;)I", (void*) untagSocketFd },
+};
+
+int register_android_net_TrafficStats(JNIEnv* env) {
+    return jniRegisterNativeMethods(env, "android/net/TrafficStats", gMethods, NELEM(gMethods));
+}
+
+};  // namespace android
+
diff --git a/packages/ConnectivityT/framework-t/jni/onload.cpp b/packages/ConnectivityT/framework-t/jni/onload.cpp
new file mode 100644
index 0000000..1fb42c6
--- /dev/null
+++ b/packages/ConnectivityT/framework-t/jni/onload.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "FrameworkConnectivityJNI"
+
+#include <log/log.h>
+#include <nativehelper/JNIHelp.h>
+
+namespace android {
+
+int register_android_net_TrafficStats(JNIEnv* env);
+
+extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
+    JNIEnv *env;
+    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+        __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "ERROR: GetEnv failed");
+        return JNI_ERR;
+    }
+
+    if (register_android_net_TrafficStats(env) < 0) return JNI_ERR;
+
+    return JNI_VERSION_1_6;
+}
+
+};  // namespace android
+
diff --git a/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java b/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java
index c2f0cdf..bc836d8 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java
@@ -31,12 +31,9 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.RemoteException;
+import android.os.StrictMode;
 import android.util.Log;
 
-import com.android.server.NetworkManagementSocketTagger;
-
-import dalvik.system.SocketTagger;
-
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.net.DatagramSocket;
@@ -56,6 +53,10 @@
  * use {@link NetworkStatsManager} instead.
  */
 public class TrafficStats {
+    static {
+        System.loadLibrary("framework-connectivity-tiramisu-jni");
+    }
+
     private static final String TAG = TrafficStats.class.getSimpleName();
     /**
      * The return value to indicate that the device does not support the statistic.
@@ -232,9 +233,68 @@
      */
     @SystemApi(client = MODULE_LIBRARIES)
     public static void attachSocketTagger() {
-        NetworkManagementSocketTagger.install();
+        dalvik.system.SocketTagger.set(new SocketTagger());
     }
 
+    private static class SocketTagger extends dalvik.system.SocketTagger {
+
+        // TODO: set to false
+        private static final boolean LOGD = true;
+
+        SocketTagger() {
+        }
+
+        @Override
+        public void tag(FileDescriptor fd) throws SocketException {
+            final UidTag tagInfo = sThreadUidTag.get();
+            if (LOGD) {
+                Log.d(TAG, "tagSocket(" + fd.getInt$() + ") with statsTag=0x"
+                        + Integer.toHexString(tagInfo.tag) + ", statsUid=" + tagInfo.uid);
+            }
+            if (tagInfo.tag == -1) {
+                StrictMode.noteUntaggedSocket();
+            }
+
+            if (tagInfo.tag == -1 && tagInfo.uid == -1) return;
+            final int errno = native_tagSocketFd(fd, tagInfo.tag, tagInfo.uid);
+            if (errno < 0) {
+                Log.i(TAG, "tagSocketFd(" + fd.getInt$() + ", "
+                        + tagInfo.tag + ", "
+                        + tagInfo.uid + ") failed with errno" + errno);
+            }
+        }
+
+        @Override
+        public void untag(FileDescriptor fd) throws SocketException {
+            if (LOGD) {
+                Log.i(TAG, "untagSocket(" + fd.getInt$() + ")");
+            }
+
+            final UidTag tagInfo = sThreadUidTag.get();
+            if (tagInfo.tag == -1 && tagInfo.uid == -1) return;
+
+            final int errno = native_untagSocketFd(fd);
+            if (errno < 0) {
+                Log.w(TAG, "untagSocket(" + fd.getInt$() + ") failed with errno " + errno);
+            }
+        }
+    }
+
+    private static native int native_tagSocketFd(FileDescriptor fd, int tag, int uid);
+    private static native int native_untagSocketFd(FileDescriptor fd);
+
+    private static class UidTag {
+        public int tag = -1;
+        public int uid = -1;
+    }
+
+    private static ThreadLocal<UidTag> sThreadUidTag = new ThreadLocal<UidTag>() {
+        @Override
+        protected UidTag initialValue() {
+            return new UidTag();
+        }
+    };
+
     /**
      * Set active tag to use when accounting {@link Socket} traffic originating
      * from the current thread. Only one active tag per thread is supported.
@@ -249,7 +309,7 @@
      * @see #clearThreadStatsTag()
      */
     public static void setThreadStatsTag(int tag) {
-        NetworkManagementSocketTagger.setThreadSocketStatsTag(tag);
+        getAndSetThreadStatsTag(tag);
     }
 
     /**
@@ -267,7 +327,9 @@
      *         restore any existing values after a nested operation is finished
      */
     public static int getAndSetThreadStatsTag(int tag) {
-        return NetworkManagementSocketTagger.setThreadSocketStatsTag(tag);
+        final int old = sThreadUidTag.get().tag;
+        sThreadUidTag.get().tag = tag;
+        return old;
     }
 
     /**
@@ -327,7 +389,7 @@
      * @see #setThreadStatsTag(int)
      */
     public static int getThreadStatsTag() {
-        return NetworkManagementSocketTagger.getThreadSocketStatsTag();
+        return sThreadUidTag.get().tag;
     }
 
     /**
@@ -337,7 +399,7 @@
      * @see #setThreadStatsTag(int)
      */
     public static void clearThreadStatsTag() {
-        NetworkManagementSocketTagger.setThreadSocketStatsTag(-1);
+        sThreadUidTag.get().tag = -1;
     }
 
     /**
@@ -357,7 +419,7 @@
      */
     @SuppressLint("RequiresPermission")
     public static void setThreadStatsUid(int uid) {
-        NetworkManagementSocketTagger.setThreadSocketStatsUid(uid);
+        sThreadUidTag.get().uid = uid;
     }
 
     /**
@@ -368,7 +430,7 @@
      * @see #setThreadStatsUid(int)
      */
     public static int getThreadStatsUid() {
-        return NetworkManagementSocketTagger.getThreadSocketStatsUid();
+        return sThreadUidTag.get().uid;
     }
 
     /**
@@ -395,7 +457,7 @@
      */
     @SuppressLint("RequiresPermission")
     public static void clearThreadStatsUid() {
-        NetworkManagementSocketTagger.setThreadSocketStatsUid(-1);
+        setThreadStatsUid(-1);
     }
 
     /**
diff --git a/packages/ConnectivityT/framework-t/src/com/android/server/NetworkManagementSocketTagger.java b/packages/ConnectivityT/framework-t/src/com/android/server/NetworkManagementSocketTagger.java
deleted file mode 100644
index 8bb12a6d..0000000
--- a/packages/ConnectivityT/framework-t/src/com/android/server/NetworkManagementSocketTagger.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import android.os.StrictMode;
-import android.util.Log;
-
-import dalvik.system.SocketTagger;
-
-import java.io.FileDescriptor;
-import java.net.SocketException;
-
-/**
- * Assigns tags to sockets for traffic stats.
- * @hide
- */
-public final class NetworkManagementSocketTagger extends SocketTagger {
-    private static final String TAG = "NetworkManagementSocketTagger";
-    private static final boolean LOGD = false;
-
-    private static ThreadLocal<SocketTags> threadSocketTags = new ThreadLocal<SocketTags>() {
-        @Override
-        protected SocketTags initialValue() {
-            return new SocketTags();
-        }
-    };
-
-    public static void install() {
-        SocketTagger.set(new NetworkManagementSocketTagger());
-    }
-
-    public static int setThreadSocketStatsTag(int tag) {
-        final int old = threadSocketTags.get().statsTag;
-        threadSocketTags.get().statsTag = tag;
-        return old;
-    }
-
-    public static int getThreadSocketStatsTag() {
-        return threadSocketTags.get().statsTag;
-    }
-
-    public static int setThreadSocketStatsUid(int uid) {
-        final int old = threadSocketTags.get().statsUid;
-        threadSocketTags.get().statsUid = uid;
-        return old;
-    }
-
-    public static int getThreadSocketStatsUid() {
-        return threadSocketTags.get().statsUid;
-    }
-
-    @Override
-    public void tag(FileDescriptor fd) throws SocketException {
-        final SocketTags options = threadSocketTags.get();
-        if (LOGD) {
-            Log.d(TAG, "tagSocket(" + fd.getInt$() + ") with statsTag=0x"
-                    + Integer.toHexString(options.statsTag) + ", statsUid=" + options.statsUid);
-        }
-        if (options.statsTag == -1) {
-            StrictMode.noteUntaggedSocket();
-        }
-        // TODO: skip tagging when options would be no-op
-        tagSocketFd(fd, options.statsTag, options.statsUid);
-    }
-
-    private void tagSocketFd(FileDescriptor fd, int tag, int uid) {
-        if (tag == -1 && uid == -1) return;
-
-        final int errno = native_tagSocketFd(fd, tag, uid);
-        if (errno < 0) {
-            Log.i(TAG, "tagSocketFd(" + fd.getInt$() + ", "
-                    + tag + ", "
-                    + uid + ") failed with errno" + errno);
-        }
-    }
-
-    @Override
-    public void untag(FileDescriptor fd) throws SocketException {
-        if (LOGD) {
-            Log.i(TAG, "untagSocket(" + fd.getInt$() + ")");
-        }
-        unTagSocketFd(fd);
-    }
-
-    private void unTagSocketFd(FileDescriptor fd) {
-        final SocketTags options = threadSocketTags.get();
-        if (options.statsTag == -1 && options.statsUid == -1) return;
-
-        final int errno = native_untagSocketFd(fd);
-        if (errno < 0) {
-            Log.w(TAG, "untagSocket(" + fd.getInt$() + ") failed with errno " + errno);
-        }
-    }
-
-    public static class SocketTags {
-        public int statsTag = -1;
-        public int statsUid = -1;
-    }
-
-    /**
-     * Convert {@code /proc/} tag format to {@link Integer}. Assumes incoming
-     * format like {@code 0x7fffffff00000000}.
-     */
-    public static int kernelToTag(String string) {
-        int length = string.length();
-        if (length > 10) {
-            return Long.decode(string.substring(0, length - 8)).intValue();
-        } else {
-            return 0;
-        }
-    }
-
-    private static native int native_tagSocketFd(FileDescriptor fd, int tag, int uid);
-    private static native int native_untagSocketFd(FileDescriptor fd);
-}
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java
index 17f3455..668d1cb 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java
@@ -22,8 +22,6 @@
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
 
-import static com.android.server.NetworkManagementSocketTagger.kernelToTag;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -470,6 +468,19 @@
     }
 
     /**
+     * Convert {@code /proc/} tag format to {@link Integer}. Assumes incoming
+     * format like {@code 0x7fffffff00000000}.
+     */
+    public static int kernelToTag(String string) {
+        int length = string.length();
+        if (length > 10) {
+            return Long.decode(string.substring(0, length - 8)).intValue();
+        } else {
+            return 0;
+        }
+    }
+
+    /**
      * Parse statistics from file into given {@link NetworkStats} object. Values
      * are expected to monotonically increase since device boot.
      */
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
index 1d0ae99..b65e976 100755
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
@@ -37,7 +37,7 @@
 import android.widget.Button;
 
 import com.android.internal.app.AlertActivity;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -154,8 +154,8 @@
                         final PackageLite pkg = result.getResult();
                         params.setAppPackageName(pkg.getPackageName());
                         params.setInstallLocation(pkg.getInstallLocation());
-                        params.setSize(
-                                PackageHelper.calculateInstalledSize(pkg, params.abiOverride));
+                        params.setSize(InstallLocationUtils.calculateInstalledSize(pkg,
+                                params.abiOverride));
                     }
                 } catch (IOException e) {
                     Log.e(LOG_TAG,
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 45f8f1d..af6a658 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1549,6 +1549,17 @@
     <!-- Content description of the no calling for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_no_calling">No calling.</string>
 
+    <!-- Screensaver overlay which displays the time. [CHAR LIMIT=20] -->
+    <string name="dream_complication_title_time">Time</string>
+    <!-- Screensaver overlay which displays the date. [CHAR LIMIT=20] -->
+    <string name="dream_complication_title_date">Date</string>
+    <!-- Screensaver overlay which displays the weather. [CHAR LIMIT=20] -->
+    <string name="dream_complication_title_weather">Weather</string>
+    <!-- Screensaver overlay which displays air quality. [CHAR LIMIT=20] -->
+    <string name="dream_complication_title_aqi">Air Quality</string>
+    <!-- Screensaver overlay which displays cast info. [CHAR LIMIT=20] -->
+    <string name="dream_complication_title_cast_info">Cast Info</string>
+
     <!-- Title for a screen allowing the user to choose a profile picture. [CHAR LIMIT=NONE] -->
     <string name="avatar_picker_title">Choose a profile picture</string>
 </resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
index 3f322d6..f7b2974 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
@@ -16,8 +16,11 @@
 
 package com.android.settingslib;
 
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONTROLLED_BY_ADMIN_SUMMARY;
+
 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
 
+import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.os.Build;
@@ -102,8 +105,11 @@
         if (mDisabledSummary) {
             final TextView summaryView = (TextView) holder.findViewById(android.R.id.summary);
             if (summaryView != null) {
-                final CharSequence disabledText = summaryView.getContext().getText(
-                        R.string.disabled_by_admin_summary_text);
+                final CharSequence disabledText = mContext
+                        .getSystemService(DevicePolicyManager.class)
+                        .getString(CONTROLLED_BY_ADMIN_SUMMARY,
+                                () -> summaryView.getContext().getString(
+                                        R.string.disabled_by_admin_summary_text));
                 if (mDisabledByAdmin) {
                     summaryView.setText(disabledText);
                 } else if (mDisabledByAppOps) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index d73e45e..883e080 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -1,7 +1,10 @@
 package com.android.settingslib;
 
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_USER_LABEL;
+
 import android.annotation.ColorInt;
 import android.annotation.Nullable;
+import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -124,7 +127,8 @@
         String name = info != null ? info.name : null;
         if (info.isManagedProfile()) {
             // We use predefined values for managed profiles
-            return context.getString(R.string.managed_user_title);
+            return context.getSystemService(DevicePolicyManager.class).getString(
+                    WORK_PROFILE_USER_LABEL, () -> context.getString(R.string.managed_user_title));
         } else if (info.isGuest()) {
             name = context.getString(R.string.user_guest);
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
index 46e31ce..6bf43e5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
@@ -34,6 +34,7 @@
 import android.provider.Settings;
 import android.service.dreams.DreamService;
 import android.service.dreams.IDreamManager;
+import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.Xml;
@@ -50,6 +51,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -292,6 +294,11 @@
         }
     }
 
+    /** Returns whether a particular complication is enabled */
+    public boolean isComplicationEnabled(@ComplicationType int complication) {
+        return getEnabledComplications().contains(complication);
+    }
+
     /** Gets all complications which have been enabled by the user. */
     public Set<Integer> getEnabledComplications() {
         final String enabledComplications = Settings.Secure.getString(
@@ -331,6 +338,35 @@
                 convertToString(enabledComplications));
     }
 
+    /**
+     * Gets the title of a particular complication type to be displayed to the user. If there
+     * is no title, null is returned.
+     */
+    @Nullable
+    public CharSequence getComplicationTitle(@ComplicationType int complicationType) {
+        int res = 0;
+        switch (complicationType) {
+            case COMPLICATION_TYPE_TIME:
+                res = R.string.dream_complication_title_time;
+                break;
+            case COMPLICATION_TYPE_DATE:
+                res = R.string.dream_complication_title_date;
+                break;
+            case COMPLICATION_TYPE_WEATHER:
+                res = R.string.dream_complication_title_weather;
+                break;
+            case COMPLICATION_TYPE_AIR_QUALITY:
+                res = R.string.dream_complication_title_aqi;
+                break;
+            case COMPLICATION_TYPE_CAST_INFO:
+                res = R.string.dream_complication_title_cast_info;
+                break;
+            default:
+                return null;
+        }
+        return mContext.getString(res);
+    }
+
     private static String convertToString(Set<Integer> set) {
         return set.stream()
                 .map(String::valueOf)
@@ -338,6 +374,9 @@
     }
 
     private static Set<Integer> parseFromString(String string) {
+        if (TextUtils.isEmpty(string)) {
+            return new HashSet<>();
+        }
         return Arrays.stream(string.split(","))
                 .map(Integer::parseInt)
                 .collect(Collectors.toSet());
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index dc7632d..b851232 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -46,7 +46,7 @@
 import android.text.TextUtils;
 import android.util.Log;
 
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.RILConstants;
 import com.android.internal.util.XmlUtils;
@@ -783,7 +783,7 @@
                         + " VALUES(?,?);");
                 loadSetting(stmt, Global.SET_INSTALL_LOCATION, 0);
                 loadSetting(stmt, Global.DEFAULT_INSTALL_LOCATION,
-                        PackageHelper.APP_INSTALL_AUTO);
+                        InstallLocationUtils.APP_INSTALL_AUTO);
                 db.setTransactionSuccessful();
              } finally {
                  db.endTransaction();
@@ -2534,7 +2534,7 @@
 
             loadSetting(stmt, Settings.Global.SET_INSTALL_LOCATION, 0);
             loadSetting(stmt, Settings.Global.DEFAULT_INSTALL_LOCATION,
-                    PackageHelper.APP_INSTALL_AUTO);
+                    InstallLocationUtils.APP_INSTALL_AUTO);
 
             // Set default cdma emergency tone
             loadSetting(stmt, Settings.Global.EMERGENCY_TONE, 0);
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index f3a87af..55bf6e5 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -301,6 +301,7 @@
 
     <!-- For clipboard overlay -->
     <uses-permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND" />
+    <uses-permission android:name="android.permission.SET_CLIP_SOURCE" />
 
     <!-- To change system language (HDMI CEC) -->
     <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index 2a3761e..7e31909 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -17,6 +17,7 @@
 <com.android.systemui.clipboardoverlay.DraggableConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:theme="@style/Screenshot"
     android:alpha="0"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
@@ -50,7 +51,8 @@
         <LinearLayout
             android:id="@+id/actions"
             android:layout_width="wrap_content"
-            android:layout_height="wrap_content">
+            android:layout_height="wrap_content"
+            android:animateLayoutChanges="true">
             <include layout="@layout/screenshot_action_chip"
                      android:id="@+id/remote_copy_chip"/>
             <include layout="@layout/screenshot_action_chip"
@@ -64,7 +66,7 @@
         android:layout_marginStart="@dimen/overlay_offset_x"
         android:layout_marginBottom="@dimen/overlay_offset_y"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintBottom_toBottomOf="@id/actions_container_background"
         android:elevation="@dimen/overlay_preview_elevation"
         app:layout_constraintEnd_toEndOf="@id/clipboard_preview_end"
         app:layout_constraintTop_toTopOf="@id/clipboard_preview_top"
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index db69924..de136de 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -85,7 +85,7 @@
          Contract: Pixel with fillColor blended over backgroundColor blended over translucent should
          equal to singleToneColor blended over translucent. -->
     <declare-styleable name="TonedIcon">
-        <attr name="backgroundColor" format="integer" />
+        <attr name="iconBackgroundColor" format="integer" />
         <attr name="fillColor" format="integer" />
         <attr name="singleToneColor" format="integer" />
         <attr name="homeHandleColor" format="integer" />
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index ac98739..57f1f3f 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -404,19 +404,19 @@
     </style>
 
     <style name="DualToneLightTheme">
-        <item name="backgroundColor">@color/light_mode_icon_color_dual_tone_background</item>
+        <item name="iconBackgroundColor">@color/light_mode_icon_color_dual_tone_background</item>
         <item name="fillColor">@color/light_mode_icon_color_dual_tone_fill</item>
         <item name="singleToneColor">@color/light_mode_icon_color_single_tone</item>
         <item name="homeHandleColor">@color/navigation_bar_home_handle_light_color</item>
     </style>
     <style name="DualToneDarkTheme">
-        <item name="backgroundColor">@color/dark_mode_icon_color_dual_tone_background</item>
+        <item name="iconBackgroundColor">@color/dark_mode_icon_color_dual_tone_background</item>
         <item name="fillColor">@color/dark_mode_icon_color_dual_tone_fill</item>
         <item name="singleToneColor">@color/dark_mode_icon_color_single_tone</item>
         <item name="homeHandleColor">@color/navigation_bar_home_handle_dark_color</item>
     </style>
     <style name="QSHeaderDarkTheme">
-        <item name="backgroundColor">@color/dark_mode_qs_icon_color_dual_tone_background</item>
+        <item name="iconBackgroundColor">@color/dark_mode_qs_icon_color_dual_tone_background</item>
         <item name="fillColor">@color/dark_mode_qs_icon_color_dual_tone_fill</item>
         <item name="singleToneColor">@color/dark_mode_qs_icon_color_single_tone</item>
     </style>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimator.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimator.kt
new file mode 100644
index 0000000..ffab3cd
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimator.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.shared.animation
+
+import android.view.View
+import android.view.ViewGroup
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import java.lang.ref.WeakReference
+
+/**
+ * Translates items away/towards the hinge when the device is opened/closed, according to the
+ * direction specified in [ViewIdToTranslate.direction], for a maximum of [translationMax] when
+ * progresses are 0.
+ */
+class UnfoldConstantTranslateAnimator(
+    private val viewsIdToTranslate: Set<ViewIdToTranslate>,
+    private val progressProvider: UnfoldTransitionProgressProvider
+) : TransitionProgressListener {
+
+    private var viewsToTranslate = listOf<ViewToTranslate>()
+    private lateinit var rootView: ViewGroup
+    private var translationMax = 0f
+
+    fun init(rootView: ViewGroup, translationMax: Float) {
+        this.rootView = rootView
+        this.translationMax = translationMax
+        progressProvider.addCallback(this)
+    }
+
+    override fun onTransitionStarted() {
+        registerViewsForAnimation(rootView, viewsIdToTranslate)
+    }
+
+    override fun onTransitionProgress(progress: Float) {
+        translateViews(progress)
+    }
+
+    override fun onTransitionFinished() {
+        translateViews(progress = 1f)
+    }
+
+    private fun translateViews(progress: Float) {
+        // progress == 0 -> -translationMax
+        // progress == 1 -> 0
+        val xTrans = (progress - 1f) * translationMax
+        viewsToTranslate.forEach { (view, direction, shouldBeAnimated) ->
+            if (shouldBeAnimated()) {
+                view.get()?.translationX = xTrans * direction.multiplier
+            }
+        }
+    }
+
+    /** Finds in [parent] all views specified by [ids] and register them for the animation. */
+    private fun registerViewsForAnimation(parent: ViewGroup, ids: Set<ViewIdToTranslate>) {
+        viewsToTranslate =
+            ids.mapNotNull { (id, dir, pred) ->
+                parent.findViewById<View>(id)?.let { view ->
+                    ViewToTranslate(WeakReference(view), dir, pred)
+                }
+            }
+    }
+
+    /** Represents a view to animate. [rootView] should contain a view with [viewId] inside. */
+    data class ViewIdToTranslate(
+        val viewId: Int,
+        val direction: Direction,
+        val shouldBeAnimated: () -> Boolean = { true }
+    )
+
+    private data class ViewToTranslate(
+        val view: WeakReference<View>,
+        val direction: Direction,
+        val shouldBeAnimated: () -> Boolean
+    )
+
+    /** Direction of the animation. */
+    enum class Direction(val multiplier: Float) {
+        LEFT(-1f),
+        RIGHT(1f),
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
index cb25e1a..89d6fb5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
@@ -17,11 +17,13 @@
 package com.android.keyguard
 
 import android.content.Context
-import android.view.View
 import android.view.ViewGroup
 import com.android.systemui.R
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.LEFT
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.RIGHT
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate
 import com.android.systemui.unfold.SysUIUnfoldScope
-import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
 import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
 import javax.inject.Inject
 
@@ -30,84 +32,37 @@
  * the set of ids, which also dictact which direction to move and when, via a filter function.
  */
 @SysUIUnfoldScope
-class KeyguardUnfoldTransition @Inject constructor(
-    val context: Context,
-    val unfoldProgressProvider: NaturalRotationUnfoldProgressProvider
+class KeyguardUnfoldTransition
+@Inject
+constructor(
+    private val context: Context,
+    unfoldProgressProvider: NaturalRotationUnfoldProgressProvider
 ) {
 
-    companion object {
-        final val LEFT = -1
-        final val RIGHT = 1
-    }
+    /** Certain views only need to move if they are not currently centered */
+    var statusViewCentered = false
 
     private val filterSplitShadeOnly = { !statusViewCentered }
     private val filterNever = { true }
 
-    private val ids = setOf(
-        Triple(R.id.keyguard_status_area, LEFT, filterNever),
-        Triple(R.id.controls_button, LEFT, filterNever),
-        Triple(R.id.lockscreen_clock_view_large, LEFT, filterSplitShadeOnly),
-        Triple(R.id.lockscreen_clock_view, LEFT, filterNever),
-        Triple(R.id.notification_stack_scroller, RIGHT, filterSplitShadeOnly),
-        Triple(R.id.wallet_button, RIGHT, filterNever)
-    )
-    private var parent: ViewGroup? = null
-    private var views = listOf<Triple<View, Int, () -> Boolean>>()
-    private var xTranslationMax = 0f
-
-    /**
-     * Certain views only need to move if they are not currently centered
-     */
-    var statusViewCentered = false
-
-    init {
-        unfoldProgressProvider.addCallback(
-            object : TransitionProgressListener {
-                override fun onTransitionStarted() {
-                    findViews()
-                }
-
-                override fun onTransitionProgress(progress: Float) {
-                    translateViews(progress)
-                }
-
-                override fun onTransitionFinished() {
-                    translateViews(1f)
-                }
-            }
-        )
+    private val translateAnimator by lazy {
+        UnfoldConstantTranslateAnimator(
+            viewsIdToTranslate =
+                setOf(
+                    ViewIdToTranslate(R.id.keyguard_status_area, LEFT, filterNever),
+                    ViewIdToTranslate(R.id.controls_button, LEFT, filterNever),
+                    ViewIdToTranslate(R.id.lockscreen_clock_view_large, LEFT, filterSplitShadeOnly),
+                    ViewIdToTranslate(R.id.lockscreen_clock_view, LEFT, filterNever),
+                    ViewIdToTranslate(
+                        R.id.notification_stack_scroller, RIGHT, filterSplitShadeOnly),
+                    ViewIdToTranslate(R.id.wallet_button, RIGHT, filterNever)),
+            progressProvider = unfoldProgressProvider)
     }
 
-    /**
-     * Relies on the [parent] to locate views to translate
-     */
+    /** Relies on the [parent] to locate views to translate. */
     fun setup(parent: ViewGroup) {
-        this.parent = parent
-        xTranslationMax = context.resources.getDimensionPixelSize(
-            R.dimen.keyguard_unfold_translation_x).toFloat()
-    }
-
-    /**
-     * Manually translate views based on set direction. At the moment
-     * [UnfoldMoveFromCenterAnimator] exists but moves all views a dynamic distance
-     * from their mid-point. This code instead will only ever translate by a fixed amount.
-     */
-    private fun translateViews(progress: Float) {
-        val xTrans = progress * xTranslationMax - xTranslationMax
-        views.forEach {
-            (view, direction, pred) -> if (pred()) {
-                view.setTranslationX(xTrans * direction)
-            }
-        }
-    }
-
-    private fun findViews() {
-        parent?.let { p ->
-            views = ids.mapNotNull {
-                (id, direction, pred) -> p.findViewById<View>(id)?.let {
-                    Triple(it, direction, pred)
-                }
-            }
-        }
+        val translationMax =
+            context.resources.getDimensionPixelSize(R.dimen.keyguard_unfold_translation_x).toFloat()
+        translateAnimator.init(parent, translationMax)
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index f2d0427..cc10b02 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1196,6 +1196,21 @@
         return fingerprintAllowed || faceAllowed;
     }
 
+    /**
+     * Returns whether the user is unlocked with a biometric that is currently bypassing
+     * the lock screen.
+     */
+    public boolean getUserUnlockedWithBiometricAndIsBypassing(int userId) {
+        BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(userId);
+        BiometricAuthenticated face = mUserFaceAuthenticated.get(userId);
+        // fingerprint always bypasses
+        boolean fingerprintAllowed = fingerprint != null && fingerprint.mAuthenticated
+                && isUnlockingWithBiometricAllowed(fingerprint.mIsStrongBiometric);
+        boolean faceAllowed = face != null && face.mAuthenticated
+                && isUnlockingWithBiometricAllowed(face.mIsStrongBiometric);
+        return fingerprintAllowed || faceAllowed && mKeyguardBypassController.canBypass();
+    }
+
     public boolean getUserTrustIsManaged(int userId) {
         return mUserTrustIsManaged.get(userId) && !isTrustDisabled(userId);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/DualToneHandler.kt b/packages/SystemUI/src/com/android/systemui/DualToneHandler.kt
index fdc3229..2b8d3e0 100644
--- a/packages/SystemUI/src/com/android/systemui/DualToneHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/DualToneHandler.kt
@@ -54,11 +54,11 @@
                 Utils.getThemeAttr(context, R.attr.lightIconTheme))
         darkColor = Color(
                 Utils.getColorAttrDefaultColor(dualToneDarkTheme, R.attr.singleToneColor),
-                Utils.getColorAttrDefaultColor(dualToneDarkTheme, R.attr.backgroundColor),
+                Utils.getColorAttrDefaultColor(dualToneDarkTheme, R.attr.iconBackgroundColor),
                 Utils.getColorAttrDefaultColor(dualToneDarkTheme, R.attr.fillColor))
         lightColor = Color(
                 Utils.getColorAttrDefaultColor(dualToneLightTheme, R.attr.singleToneColor),
-                Utils.getColorAttrDefaultColor(dualToneLightTheme, R.attr.backgroundColor),
+                Utils.getColorAttrDefaultColor(dualToneLightTheme, R.attr.iconBackgroundColor),
                 Utils.getColorAttrDefaultColor(dualToneLightTheme, R.attr.fillColor))
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
index 0e1cd51..72b40d4 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
@@ -63,7 +63,8 @@
             mClipboardOverlayController =
                     new ClipboardOverlayController(mContext, new TimeoutHandler(mContext));
         }
-        mClipboardOverlayController.setClipData(mClipboardManager.getPrimaryClip());
+        mClipboardOverlayController.setClipData(
+                mClipboardManager.getPrimaryClip(), mClipboardManager.getPrimaryClipSource());
         mClipboardOverlayController.setOnSessionCompleteListener(() -> {
             // Session is complete, free memory until it's needed again.
             mClipboardOverlayController = null;
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index b6bcb87..12759f48 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -182,6 +182,7 @@
         withWindowAttached(() -> {
             mWindow.setContentView(mView);
             updateInsets(mWindowManager.getCurrentWindowMetrics().getWindowInsets());
+            mView.requestLayout();
             mView.post(this::animateIn);
         });
 
@@ -213,7 +214,7 @@
         mContext.sendBroadcast(new Intent(COPY_OVERLAY_ACTION), SELF_PERMISSION);
     }
 
-    void setClipData(ClipData clipData) {
+    void setClipData(ClipData clipData, String clipSource) {
         reset();
         if (clipData == null || clipData.getItemCount() == 0) {
             showTextPreview(mContext.getResources().getString(
@@ -221,7 +222,7 @@
         } else if (!TextUtils.isEmpty(clipData.getItemAt(0).getText())) {
             ClipData.Item item = clipData.getItemAt(0);
             if (item.getTextLinks() != null) {
-                AsyncTask.execute(() -> classifyText(clipData.getItemAt(0)));
+                AsyncTask.execute(() -> classifyText(clipData.getItemAt(0), clipSource));
             }
             showEditableText(item.getText());
         } else if (clipData.getItemAt(0).getUri() != null) {
@@ -238,7 +239,7 @@
         mOnSessionCompleteListener = runnable;
     }
 
-    private void classifyText(ClipData.Item item) {
+    private void classifyText(ClipData.Item item, String source) {
         ArrayList<RemoteAction> actions = new ArrayList<>();
         for (TextLinks.TextLink link : item.getTextLinks().getLinks()) {
             TextClassification classification = mTextClassifier.classifyText(
@@ -246,14 +247,14 @@
             actions.addAll(classification.getActions());
         }
         mView.post(() -> {
-            for (ScreenshotActionChip chip : mActionChips) {
-                mActionContainer.removeView(chip);
-            }
-            mActionChips.clear();
+            resetActionChips();
             for (RemoteAction action : actions) {
-                ScreenshotActionChip chip = constructActionChip(action);
-                mActionContainer.addView(chip);
-                mActionChips.add(chip);
+                Intent targetIntent = action.getActionIntent().getIntent();
+                if (!TextUtils.equals(source, targetIntent.getComponent().getPackageName())) {
+                    ScreenshotActionChip chip = constructActionChip(action);
+                    mActionContainer.addView(chip);
+                    mActionChips.add(chip);
+                }
             }
         });
     }
@@ -451,13 +452,17 @@
         }
     }
 
-    private void reset() {
-        mView.setTranslationX(0);
-        mView.setAlpha(0);
+    private void resetActionChips() {
         for (ScreenshotActionChip chip : mActionChips) {
             mActionContainer.removeView(chip);
         }
         mActionChips.clear();
+    }
+
+    private void reset() {
+        mView.setTranslationX(0);
+        mView.setAlpha(0);
+        resetActionChips();
         mTimeoutHandler.cancelTimeout();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 4f4bd1e..be45a62 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -104,7 +104,8 @@
     private static final int MAX_NUM_LOGGED_PREDICTIONS = 10;
     private static final int MAX_NUM_LOGGED_GESTURES = 10;
 
-    static final boolean DEBUG_MISSING_GESTURE = false;
+    // Temporary log until b/202433017 is resolved
+    static final boolean DEBUG_MISSING_GESTURE = true;
     static final String DEBUG_MISSING_GESTURE_TAG = "NoBackGesture";
 
     private ISystemGestureExclusionListener mGestureExclusionListener =
@@ -318,7 +319,11 @@
             String recentsPackageName = recentsComponentName.getPackageName();
             PackageManager manager = context.getPackageManager();
             try {
-                Resources resources = manager.getResourcesForApplication(recentsPackageName);
+                Resources resources = manager.getResourcesForApplication(
+                        manager.getApplicationInfo(recentsPackageName,
+                                PackageManager.MATCH_UNINSTALLED_PACKAGES
+                                        | PackageManager.MATCH_DISABLED_COMPONENTS
+                                        | PackageManager.GET_SHARED_LIBRARY_FILES));
                 int resId = resources.getIdentifier(
                         "gesture_blocking_activities", "array", recentsPackageName);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index abfdfaf..391525e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -27,6 +27,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
@@ -47,6 +48,7 @@
 import com.android.systemui.statusbar.notification.stack.ForegroundServiceSectionController;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.Assert;
 import com.android.wm.shell.bubbles.Bubbles;
 
@@ -98,6 +100,8 @@
     private final ForegroundServiceSectionController mFgsSectionController;
     private final NotifPipelineFlags mNotifPipelineFlags;
     private AssistantFeedbackController mAssistantFeedbackController;
+    private final KeyguardStateController mKeyguardStateController;
+    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final Context mContext;
 
     private NotificationPresenter mPresenter;
@@ -129,7 +133,9 @@
             DynamicChildBindController dynamicChildBindController,
             LowPriorityInflationHelper lowPriorityInflationHelper,
             AssistantFeedbackController assistantFeedbackController,
-            NotifPipelineFlags notifPipelineFlags) {
+            NotifPipelineFlags notifPipelineFlags,
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            KeyguardStateController keyguardStateController) {
         mContext = context;
         mHandler = mainHandler;
         mFeatureFlags = featureFlags;
@@ -149,6 +155,8 @@
         mDynamicChildBindController = dynamicChildBindController;
         mLowPriorityInflationHelper = lowPriorityInflationHelper;
         mAssistantFeedbackController = assistantFeedbackController;
+        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mKeyguardStateController = keyguardStateController;
     }
 
     public void setUpWithPresenter(NotificationPresenter presenter,
@@ -174,6 +182,11 @@
 
         beginUpdate();
 
+        boolean dynamicallyUnlocked = mDynamicPrivacyController.isDynamicallyUnlocked()
+                && !(mStatusBarStateController.getState() == StatusBarState.KEYGUARD
+                && mKeyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing(
+                KeyguardUpdateMonitor.getCurrentUser()))
+                && !mKeyguardStateController.isKeyguardGoingAway();
         List<NotificationEntry> activeNotifications = mEntryManager.getVisibleNotifications();
         ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size());
         final int N = activeNotifications.size();
@@ -192,7 +205,7 @@
             boolean devicePublic = mLockscreenUserManager.isLockscreenPublicMode(currentUserId);
             boolean userPublic = devicePublic
                     || mLockscreenUserManager.isLockscreenPublicMode(userId);
-            if (userPublic && mDynamicPrivacyController.isDynamicallyUnlocked()
+            if (userPublic && dynamicallyUnlocked
                     && (userId == currentUserId || userId == UserHandle.USER_ALL
                     || !mLockscreenUserManager.needsSeparateWorkChallenge(userId))) {
                 userPublic = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index bd948ece..f56602e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -518,6 +518,7 @@
     }
 
     private void recordHistoricalState(int newState, int lastState, boolean upcoming) {
+        Trace.traceCounter(Trace.TRACE_TAG_APP, "statusBarState", newState);
         mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE;
         HistoricalState state = mHistoricalRecords[mHistoryIndex];
         state.mNewState = newState;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
index d574cda..e3d0d98 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -23,6 +23,7 @@
 import android.service.dreams.IDreamManager;
 
 import com.android.internal.statusbar.IStatusBarService;
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.dagger.SysUISingleton;
@@ -72,6 +73,7 @@
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallFlags;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLogger;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.RemoteInputUriController;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
 import com.android.systemui.tracing.ProtoTracer;
@@ -214,7 +216,9 @@
             DynamicChildBindController dynamicChildBindController,
             LowPriorityInflationHelper lowPriorityInflationHelper,
             AssistantFeedbackController assistantFeedbackController,
-            NotifPipelineFlags notifPipelineFlags) {
+            NotifPipelineFlags notifPipelineFlags,
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            KeyguardStateController keyguardStateController) {
         return new NotificationViewHierarchyManager(
                 context,
                 mainHandler,
@@ -231,7 +235,9 @@
                 dynamicChildBindController,
                 lowPriorityInflationHelper,
                 assistantFeedbackController,
-                notifPipelineFlags);
+                notifPipelineFlags,
+                keyguardUpdateMonitor,
+                keyguardStateController);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
index 0ce07cb..22300d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
@@ -98,6 +98,8 @@
         setupInvalidateNotifListCallbacks();
         // Filter at the "finalize" stage so that views remain bound by PreparationCoordinator
         pipeline.addFinalizeFilter(mNotifFilter);
+
+        updateSectionHeadersVisibility();
     }
 
     private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
@@ -164,6 +166,8 @@
         }
     }
 
+    // TODO(b/206118999): merge this class with SensitiveContentCoordinator which also depends on
+    // these same updates
     private void setupInvalidateNotifListCallbacks() {
         // register onKeyguardShowing callback
         mKeyguardStateController.addCallback(mKeyguardCallback);
@@ -220,10 +224,7 @@
     }
 
     private void invalidateListFromFilter(String reason) {
-        boolean onKeyguard = mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
-        boolean neverShowSections = mSectionHeaderVisibilityProvider.getNeverShowSectionHeaders();
-        boolean showSections = !onKeyguard && !neverShowSections;
-        mSectionHeaderVisibilityProvider.setSectionHeadersVisible(showSections);
+        updateSectionHeadersVisibility();
         mNotifFilter.invalidateList();
     }
 
@@ -235,6 +236,13 @@
                         1) == 0;
     }
 
+    private void updateSectionHeadersVisibility() {
+        boolean onKeyguard = mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
+        boolean neverShowSections = mSectionHeaderVisibilityProvider.getNeverShowSectionHeaders();
+        boolean showSections = !onKeyguard && !neverShowSections;
+        mSectionHeaderVisibilityProvider.setSectionHeadersVisible(showSections);
+    }
+
     private final KeyguardStateController.Callback mKeyguardCallback =
             new KeyguardStateController.Callback() {
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
index a115e04..9c82cb6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
@@ -17,7 +17,10 @@
 package com.android.systemui.statusbar.notification.collection.coordinator
 
 import android.os.UserHandle
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.notification.DynamicPrivacyController
 import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.ListEntry
@@ -26,6 +29,7 @@
 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
+import com.android.systemui.statusbar.policy.KeyguardStateController
 import dagger.Module
 import dagger.Provides
 
@@ -36,9 +40,13 @@
     @CoordinatorScope
     fun provideCoordinator(
         dynamicPrivacyController: DynamicPrivacyController,
-        lockscreenUserManager: NotificationLockscreenUserManager
+        lockscreenUserManager: NotificationLockscreenUserManager,
+        keyguardUpdateMonitor: KeyguardUpdateMonitor,
+        statusBarStateController: StatusBarStateController,
+        keyguardStateController: KeyguardStateController
     ): SensitiveContentCoordinator =
-            SensitiveContentCoordinatorImpl(dynamicPrivacyController, lockscreenUserManager)
+            SensitiveContentCoordinatorImpl(dynamicPrivacyController, lockscreenUserManager,
+            keyguardUpdateMonitor, statusBarStateController, keyguardStateController)
 }
 
 /** Coordinates re-inflation and post-processing of sensitive notification content. */
@@ -46,7 +54,10 @@
 
 private class SensitiveContentCoordinatorImpl(
     private val dynamicPrivacyController: DynamicPrivacyController,
-    private val lockscreenUserManager: NotificationLockscreenUserManager
+    private val lockscreenUserManager: NotificationLockscreenUserManager,
+    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+    private val statusBarStateController: StatusBarStateController,
+    private val keyguardStateController: KeyguardStateController
 ) : Invalidator("SensitiveContentInvalidator"),
         SensitiveContentCoordinator,
         DynamicPrivacyController.Listener,
@@ -61,6 +72,19 @@
     override fun onDynamicPrivacyChanged(): Unit = invalidateList()
 
     override fun onBeforeRenderList(entries: List<ListEntry>) {
+        if (keyguardStateController.isKeyguardGoingAway() ||
+                statusBarStateController.getState() == StatusBarState.KEYGUARD &&
+                keyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing(
+                        KeyguardUpdateMonitor.getCurrentUser())) {
+            // don't update yet if:
+            // - the keyguard is currently going away
+            // - LS is about to be dismissed by a biometric that bypasses LS (avoid notif flash)
+
+            // TODO(b/206118999): merge this class with KeyguardCoordinator which ensures the
+            // dependent state changes invalidate the pipeline
+            return
+        }
+
         val currentUserId = lockscreenUserManager.currentUserId
         val devicePublic = lockscreenUserManager.isLockscreenPublicMode(currentUserId)
         val deviceSensitive = devicePublic &&
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
index 28cd285..386e2d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
@@ -104,19 +104,14 @@
                 views.remove(childNode.controller.view)
             }
 
-            if (childCompletelyRemoved && parentSpec == null) {
-                // If both the child and the parent are being removed at the same time, then
-                // keep the child attached to the parent for animation purposes
-                logger.logSkippingDetach(childNode.label, parentNode.label)
-            } else {
-                logger.logDetachingChild(
-                        childNode.label,
-                        !childCompletelyRemoved,
-                        parentNode.label,
-                        newParentNode?.label)
-                parentNode.removeChild(childNode, !childCompletelyRemoved)
-                childNode.parent = null
-            }
+            logger.logDetachingChild(
+                key = childNode.label,
+                isTransfer = !childCompletelyRemoved,
+                isParentRemoved = parentSpec == null,
+                oldParent = parentNode.label,
+                newParent = newParentNode?.label)
+            parentNode.removeChild(childNode, isTransfer = !childCompletelyRemoved)
+            childNode.parent = null
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
index d274550..4c03572 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
@@ -28,25 +28,18 @@
     fun logDetachingChild(
         key: String,
         isTransfer: Boolean,
+        isParentRemoved: Boolean,
         oldParent: String?,
         newParent: String?
     ) {
         buffer.log(TAG, LogLevel.DEBUG, {
             str1 = key
             bool1 = isTransfer
+            bool2 = isParentRemoved
             str2 = oldParent
             str3 = newParent
         }, {
-            "Detach $str1 isTransfer=$bool1 oldParent=$str2 newParent=$str3"
-        })
-    }
-
-    fun logSkippingDetach(key: String, parent: String?) {
-        buffer.log(TAG, LogLevel.DEBUG, {
-            str1 = key
-            str2 = parent
-        }, {
-            "Skipping detach of $str1 because its parent $str2 is also being detached"
+            "Detach $str1 isTransfer=$bool1 isParentRemoved=$bool2 oldParent=$str2 newParent=$str3"
         })
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index dbd22db..1f7d930 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -49,6 +49,7 @@
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.Trace;
 import android.provider.Settings;
 import android.service.notification.StatusBarNotification;
 import android.util.ArraySet;
@@ -1246,6 +1247,7 @@
     }
 
     private void reInflateViews() {
+        Trace.beginSection("ExpandableNotificationRow#reInflateViews");
         // Let's update our childrencontainer. This is intentionally not guarded with
         // mIsSummaryWithChildren since we might have had children but not anymore.
         if (mChildrenContainer != null) {
@@ -1277,6 +1279,7 @@
         RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
         params.setNeedsReinflation(true);
         mRowContentBindStage.requestRebind(mEntry, null /* callback */);
+        Trace.endSection();
     }
 
     @Override
@@ -1737,6 +1740,29 @@
     }
 
     @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        Trace.beginSection(appendTraceStyleTag("ExpNotRow#onMeasure"));
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        Trace.endSection();
+    }
+
+    /** Generates and appends "(MessagingStyle)" type tag to passed string for tracing. */
+    @NonNull
+    private String appendTraceStyleTag(@NonNull String traceTag) {
+        if (!Trace.isEnabled()) {
+            return traceTag;
+        }
+
+        Class<? extends Notification.Style> style =
+                getEntry().getSbn().getNotification().getNotificationStyle();
+        if (style == null) {
+            return traceTag + "(nostyle)";
+        } else {
+            return traceTag + "(" + style.getSimpleName() + ")";
+        }
+    }
+
+    @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
         mPublicLayout = findViewById(R.id.expandedPublic);
@@ -2542,6 +2568,7 @@
 
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        Trace.beginSection(appendTraceStyleTag("ExpNotRow#onLayout"));
         int intrinsicBefore = getIntrinsicHeight();
         super.onLayout(changed, left, top, right, bottom);
         if (intrinsicBefore != getIntrinsicHeight()
@@ -2555,6 +2582,7 @@
         if (mLayoutListener != null) {
             mLayoutListener.onLayout();
         }
+        Trace.endSection();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index 89b5aef..7c9df42 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -344,9 +344,11 @@
         for (NotificationStackScrollLayout.AnimationEvent event : animationEvents) {
             final ExpandableView changingView = (ExpandableView) event.mChangingView;
             boolean loggable = false;
+            boolean isHeadsUp = false;
             String key = null;
             if (changingView instanceof ExpandableNotificationRow && mLogger != null) {
                 loggable = true;
+                isHeadsUp = ((ExpandableNotificationRow) changingView).isHeadsUp();
                 key = ((ExpandableNotificationRow) changingView).getEntry().getKey();
             }
             if (event.animationType ==
@@ -358,6 +360,9 @@
                     // The position for this child was never generated, let's continue.
                     continue;
                 }
+                if (loggable && isHeadsUp) {
+                    mLogger.logHUNViewAppearingWithAddEvent(key);
+                }
                 viewState.applyToView(changingView);
                 mNewAddChildren.add(changingView);
 
@@ -399,9 +404,18 @@
                     translationDirection = Math.max(Math.min(translationDirection, 1.0f),-1.0f);
 
                 }
+                Runnable postAnimation = changingView::removeFromTransientContainer;
+                if (loggable && isHeadsUp) {
+                    mLogger.logHUNViewDisappearingWithRemoveEvent(key);
+                    String finalKey = key;
+                    postAnimation = () -> {
+                        mLogger.disappearAnimationEnded(finalKey);
+                        changingView.removeFromTransientContainer();
+                    };
+                }
                 changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR,
                         0 /* delay */, translationDirection, false /* isHeadsUpAppear */,
-                        0, changingView::removeFromTransientContainer, null);
+                        0, postAnimation, null);
             } else if (event.animationType ==
                 NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) {
                 if (mHostLayout.isFullySwipedOut(changingView)) {
@@ -431,8 +445,7 @@
                 // this only captures HEADS_UP_APPEAR animations, but HUNs can appear with normal
                 // ADD animations, which would not be logged here.
                 if (loggable) {
-                    mLogger.logHUNViewAppearing(
-                            ((ExpandableNotificationRow) changingView).getEntry().getKey());
+                    mLogger.logHUNViewAppearing(key);
                 }
 
                 mTmpState.applyToView(changingView);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
index 4315265..77377af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
@@ -24,6 +24,22 @@
         })
     }
 
+    fun logHUNViewDisappearingWithRemoveEvent(key: String) {
+        buffer.log(TAG, LogLevel.ERROR, {
+            str1 = key
+        }, {
+            "Heads up view disappearing $str1 for ANIMATION_TYPE_REMOVE"
+        })
+    }
+
+    fun logHUNViewAppearingWithAddEvent(key: String) {
+        buffer.log(TAG, LogLevel.ERROR, {
+            str1 = key
+        }, {
+            "Heads up view disappearing $str1 for ANIMATION_TYPE_ADD"
+        })
+    }
+
     fun disappearAnimationEnded(key: String) {
         buffer.log(TAG, LogLevel.INFO, {
             str1 = key
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index febf2b5..ebfed1a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -135,7 +135,7 @@
         }
     }.setDuration(CONTENT_FADE_DURATION);
 
-    private static final int MAX_ICONS_ON_AOD = 5;
+    private static final int MAX_ICONS_ON_AOD = 3;
     public static final int MAX_ICONS_ON_LOCKSCREEN = 3;
     public static final int MAX_STATIC_ICONS = 4;
     private static final int MAX_DOTS = 1;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelUnfoldAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelUnfoldAnimationController.kt
new file mode 100644
index 0000000..ff48755
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelUnfoldAnimationController.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import android.content.Context
+import android.view.ViewGroup
+import com.android.systemui.R
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.LEFT
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.RIGHT
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate
+import com.android.systemui.unfold.SysUIUnfoldScope
+import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
+import javax.inject.Inject
+
+@SysUIUnfoldScope
+class NotificationPanelUnfoldAnimationController
+@Inject
+constructor(private val context: Context, progressProvider: NaturalRotationUnfoldProgressProvider) {
+
+    private val translateAnimator by lazy {
+        UnfoldConstantTranslateAnimator(
+            viewsIdToTranslate =
+                setOf(
+                    ViewIdToTranslate(R.id.quick_settings_panel, LEFT),
+                    ViewIdToTranslate(R.id.notification_stack_scroller, RIGHT),
+                    ViewIdToTranslate(R.id.rightLayout, RIGHT),
+                    ViewIdToTranslate(R.id.clock, LEFT),
+                    ViewIdToTranslate(R.id.date, LEFT)),
+            progressProvider = progressProvider)
+    }
+
+    fun setup(root: ViewGroup) {
+        val translationMax =
+            context.resources.getDimensionPixelSize(R.dimen.notification_side_paddings).toFloat()
+        translateAnimator.init(root, translationMax)
+    }
+}
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 bb0ed95..1870588 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -669,6 +669,8 @@
     private boolean mStatusViewCentered = true;
 
     private Optional<KeyguardUnfoldTransition> mKeyguardUnfoldTransition;
+    private Optional<NotificationPanelUnfoldAnimationController>
+            mNotificationPanelUnfoldAnimationController;
 
     private final ListenerSet<Callbacks> mNotifEventSourceCallbacks = new ListenerSet<>();
 
@@ -929,6 +931,8 @@
 
         mMaxKeyguardNotifications = resources.getInteger(R.integer.keyguard_max_notification_count);
         mKeyguardUnfoldTransition = unfoldComponent.map(c -> c.getKeyguardUnfoldTransition());
+        mNotificationPanelUnfoldAnimationController = unfoldComponent.map(
+                SysUIUnfoldComponent::getNotificationPanelUnfoldAnimationController);
 
         mCommunalSourceMonitorCallback = (source) -> {
             mUiExecutor.execute(() -> setCommunalSource(source));
@@ -1064,6 +1068,8 @@
 
         mTapAgainViewController.init();
         mKeyguardUnfoldTransition.ifPresent(u -> u.setup(mView));
+        mNotificationPanelUnfoldAnimationController.ifPresent(controller ->
+                controller.setup(mNotificationContainerParent));
     }
 
     @Override
@@ -1319,6 +1325,7 @@
         setKeyguardBottomAreaVisibility(mBarState, false);
 
         mKeyguardUnfoldTransition.ifPresent(u -> u.setup(mView));
+        mNotificationPanelUnfoldAnimationController.ifPresent(u -> u.setup(mView));
     }
 
     private void attachSplitShadeMediaPlayerContainer(FrameLayout container) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
index 091831f..ea61a8b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
@@ -113,14 +113,16 @@
      * the animation ends
      */
     fun shouldDelayKeyguardShow(): Boolean =
-        animations.any { it.shouldPlayAnimation() }
+        animations.any { it.shouldDelayKeyguardShow() }
 
     /**
      * Return true while we want to ignore requests to show keyguard, we need to handle pending
      * keyguard lock requests manually
+     *
+     * @see [com.android.systemui.keyguard.KeyguardViewMediator.maybeHandlePendingLock]
      */
     fun isKeyguardShowDelayed(): Boolean =
-        animations.any { it.isAnimationPlaying() }
+        animations.any { it.isKeyguardShowDelayed() }
 
     /**
      * Return true to ignore requests to hide keyguard
@@ -211,6 +213,8 @@
     fun shouldAnimateInKeyguard(): Boolean = false
     fun animateInKeyguard(keyguardView: View, after: Runnable) = after.run()
 
+    fun shouldDelayKeyguardShow(): Boolean = false
+    fun isKeyguardShowDelayed(): Boolean = false
     fun isKeyguardHideDelayed(): Boolean = false
     fun shouldHideScrimOnWakeUp(): Boolean = false
     fun overrideNotificationsDozeAmount(): Boolean = false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index a144533..2f3300a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -2987,6 +2987,7 @@
     }
 
     public void showKeyguardImpl() {
+        Trace.beginSection("StatusBar#showKeyguard");
         mIsKeyguard = true;
         // In case we're locking while a smartspace transition is in progress, reset it.
         mKeyguardUnlockAnimationController.resetSmartspaceTransition();
@@ -3001,6 +3002,7 @@
             mStatusBarStateController.setState(StatusBarState.KEYGUARD);
         }
         updatePanelExpansionForKeyguard();
+        Trace.endSection();
     }
 
     private void updatePanelExpansionForKeyguard() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index b203496..b833c89 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -29,6 +29,7 @@
 import android.hardware.biometrics.BiometricSourceType;
 import android.os.Bundle;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
@@ -374,6 +375,7 @@
      */
     @Override
     public void show(Bundle options) {
+        Trace.beginSection("StatusBarKeyguardViewManager#show");
         mShowing = true;
         mNotificationShadeWindowController.setKeyguardShowing(true);
         mKeyguardStateController.notifyKeyguardState(mShowing,
@@ -381,6 +383,7 @@
         reset(true /* hideBouncerWhenShowing */);
         SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED,
                 SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN);
+        Trace.endSection();
     }
 
     /**
@@ -715,6 +718,7 @@
 
     @Override
     public void hide(long startTime, long fadeoutDuration) {
+        Trace.beginSection("StatusBarKeyguardViewManager#hide");
         mShowing = false;
         mKeyguardStateController.notifyKeyguardState(mShowing,
                 mKeyguardStateController.isOccluded());
@@ -814,6 +818,7 @@
         }
         SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED,
                 SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__HIDDEN);
+        Trace.endSection();
     }
 
     private boolean needsBypassFading() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index cc65ca02..0abe8e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -136,6 +136,12 @@
                 globalSettings.getFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f)
     }
 
+    override fun shouldDelayKeyguardShow(): Boolean =
+        shouldPlayAnimation()
+
+    override fun isKeyguardShowDelayed(): Boolean =
+        isAnimationPlaying()
+
     /**
      * Animates in the provided keyguard view, ending in the same position that it will be in on
      * AOD.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 48949f92..3205e09 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -118,6 +118,7 @@
     private boolean mColorized;
     private int mTint;
     private boolean mResetting;
+    private boolean mWasSpinning;
 
     // TODO(b/193539698): move these to a Controller
     private RemoteInputController mController;
@@ -439,6 +440,10 @@
                 mEditText.requestFocus();
             }
         }
+        if (mWasSpinning) {
+            mController.addSpinning(mEntry.getKey(), mToken);
+            mWasSpinning = false;
+        }
     }
 
     @Override
@@ -447,6 +452,7 @@
         mEditText.removeTextChangedListener(mTextWatcher);
         mEditText.setOnEditorActionListener(null);
         mEditText.mRemoteInputView = null;
+        mWasSpinning = mController.isSpinning(mEntry.getKey(), mToken);
         if (mEntry.getRow().isChangingPosition() || isTemporarilyDetached()) {
             return;
         }
@@ -533,6 +539,8 @@
         if (isActive() && mWrapper != null) {
             mWrapper.setRemoteInputVisible(true);
         }
+
+        mWasSpinning = false;
     }
 
     private void reset() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 9f20bc5..49e712d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -455,6 +455,13 @@
         }
     }
 
+    /**
+     * Returns whether the current user is a system user.
+     */
+    public boolean isSystemUser() {
+        return mUserTracker.getUserId() == UserHandle.USER_SYSTEM;
+    }
+
     public void removeUserId(int userId) {
         if (userId == UserHandle.USER_SYSTEM) {
             Log.w(TAG, "User " + userId + " could not removed.");
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
index c481fc9..2e627a8 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
@@ -20,7 +20,6 @@
 import android.os.PowerManager
 import android.provider.Settings
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.keyguard.KeyguardViewMediator
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.statusbar.LightRevealScrim
 import com.android.systemui.statusbar.phone.ScreenOffAnimation
@@ -28,7 +27,6 @@
 import com.android.systemui.statusbar.policy.CallbackController
 import com.android.systemui.unfold.FoldAodAnimationController.FoldAodAnimationStatus
 import com.android.systemui.util.settings.GlobalSettings
-import dagger.Lazy
 import javax.inject.Inject
 
 /**
@@ -40,7 +38,6 @@
 @Inject
 constructor(
     @Main private val handler: Handler,
-    private val keyguardViewMediatorLazy: Lazy<KeyguardViewMediator>,
     private val wakefulnessLifecycle: WakefulnessLifecycle,
     private val globalSettings: GlobalSettings
 ) : CallbackController<FoldAodAnimationStatus>, ScreenOffAnimation, WakefulnessLifecycle.Observer {
@@ -57,7 +54,6 @@
         statusBar.notificationPanelViewController.startFoldToAodAnimation {
             // End action
             isAnimationPlaying = false
-            keyguardViewMediatorLazy.get().maybeHandlePendingLock()
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
index 07f9c54..7350b37 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
@@ -18,6 +18,7 @@
 
 import com.android.keyguard.KeyguardUnfoldTransition
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.phone.NotificationPanelUnfoldAnimationController
 import com.android.systemui.statusbar.phone.StatusBarMoveFromCenterAnimationController
 import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
 import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
@@ -85,6 +86,8 @@
 
     fun getStatusBarMoveFromCenterAnimationController(): StatusBarMoveFromCenterAnimationController
 
+    fun getNotificationPanelUnfoldAnimationController(): NotificationPanelUnfoldAnimationController
+
     fun getFoldAodAnimationController(): FoldAodAnimationController
 
     fun getUnfoldTransitionWallpaperController(): UnfoldTransitionWallpaperController
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/Observer.java b/packages/SystemUI/src/com/android/systemui/util/service/Observer.java
new file mode 100644
index 0000000..7687432
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/service/Observer.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.service;
+
+/**
+ * The {@link Observer} interface specifies an entity which listeners
+ * can be informed of changes to the source, which will require updating. Note that this deals
+ * with changes to the source itself, not content which will be updated through the interface.
+ */
+public interface Observer {
+    /**
+     * Callback for receiving updates from the {@link Observer}.
+     */
+    interface Callback {
+        /**
+         * Invoked when the source has changed.
+         */
+        void onSourceChanged();
+    }
+
+    /**
+     * Adds a callback to receive future updates from the {@link Observer}.
+     */
+    void addCallback(Callback callback);
+
+    /**
+     * Removes a callback from receiving further updates.
+     * @param callback
+     */
+    void removeCallback(Callback callback);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/PackageObserver.java b/packages/SystemUI/src/com/android/systemui/util/service/PackageObserver.java
new file mode 100644
index 0000000..2ee7b20
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/service/PackageObserver.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.service;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.PatternMatcher;
+import android.util.Log;
+
+import com.android.systemui.communal.CommunalSource;
+
+import com.google.android.collect.Lists;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import javax.inject.Inject;
+
+/**
+ * {@link PackageObserver} allows for monitoring the system for changes relating to a particular
+ * package. This can be used by {@link CommunalSource} clients to detect when a related package
+ * has changed and reloading is necessary.
+ */
+public class PackageObserver implements Observer {
+    private static final String TAG = "PackageObserver";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private final ArrayList<WeakReference<Callback>> mCallbacks = Lists.newArrayList();
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (DEBUG) {
+                Log.d(TAG, "package added receiver - onReceive");
+            }
+
+            final Iterator<WeakReference<Callback>> iter = mCallbacks.iterator();
+            while (iter.hasNext()) {
+                final Callback callback = iter.next().get();
+                if (callback != null) {
+                    callback.onSourceChanged();
+                } else {
+                    iter.remove();
+                }
+            }
+        }
+    };
+
+    private final String mPackageName;
+    private final Context mContext;
+
+    @Inject
+    public PackageObserver(Context context, ComponentName component) {
+        mContext = context;
+        mPackageName = component.getPackageName();
+    }
+
+    @Override
+    public void addCallback(Callback callback) {
+        if (DEBUG) {
+            Log.d(TAG, "addCallback:" + callback);
+        }
+        mCallbacks.add(new WeakReference<>(callback));
+
+        // Only register for listening to package additions on first callback.
+        if (mCallbacks.size() > 1) {
+            return;
+        }
+
+        final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+        filter.addDataScheme("package");
+        filter.addDataSchemeSpecificPart(mPackageName, PatternMatcher.PATTERN_LITERAL);
+        // Note that we directly register the receiver here as data schemes are not supported by
+        // BroadcastDispatcher.
+        mContext.registerReceiver(mReceiver, filter, Context.RECEIVER_EXPORTED);
+    }
+
+    @Override
+    public void removeCallback(Callback callback) {
+        if (DEBUG) {
+            Log.d(TAG, "removeCallback:" + callback);
+        }
+        final boolean removed = mCallbacks.removeIf(el -> el.get() == callback);
+
+        if (removed && mCallbacks.isEmpty()) {
+            mContext.unregisterReceiver(mReceiver);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java b/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
new file mode 100644
index 0000000..292c062
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.service;
+
+import static com.android.systemui.util.service.dagger.ObservableServiceModule.BASE_RECONNECT_DELAY_MS;
+import static com.android.systemui.util.service.dagger.ObservableServiceModule.MAX_RECONNECT_ATTEMPTS;
+import static com.android.systemui.util.service.dagger.ObservableServiceModule.MIN_CONNECTION_DURATION_MS;
+import static com.android.systemui.util.service.dagger.ObservableServiceModule.OBSERVER;
+import static com.android.systemui.util.service.dagger.ObservableServiceModule.SERVICE_CONNECTION;
+
+import android.util.Log;
+
+import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.time.SystemClock;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * The {@link PersistentConnectionManager} is responsible for maintaining a connection to a
+ * {@link ObservableServiceConnection}.
+ * @param <T> The transformed connection type handled by the service.
+ */
+public class PersistentConnectionManager<T> {
+    private static final String TAG = "PersistentConnManager";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private final SystemClock mSystemClock;
+    private final DelayableExecutor mMainExecutor;
+    private final int mBaseReconnectDelayMs;
+    private final int mMaxReconnectAttempts;
+    private final int mMinConnectionDuration;
+    private final Observer mObserver;
+
+    private int mReconnectAttempts = 0;
+    private Runnable mCurrentReconnectCancelable;
+
+    private final ObservableServiceConnection<T> mConnection;
+
+    private final Runnable mConnectRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mCurrentReconnectCancelable = null;
+            mConnection.bind();
+        }
+    };
+
+    private final Observer.Callback mObserverCallback = () -> initiateConnectionAttempt();
+
+    private final ObservableServiceConnection.Callback mConnectionCallback =
+            new ObservableServiceConnection.Callback() {
+        private long mStartTime;
+
+        @Override
+        public void onConnected(ObservableServiceConnection connection, Object proxy) {
+            mStartTime = mSystemClock.currentTimeMillis();
+        }
+
+        @Override
+        public void onDisconnected(ObservableServiceConnection connection, int reason) {
+            if (mSystemClock.currentTimeMillis() - mStartTime > mMinConnectionDuration) {
+                initiateConnectionAttempt();
+            } else {
+                scheduleConnectionAttempt();
+            }
+        }
+    };
+
+    @Inject
+    public PersistentConnectionManager(
+            SystemClock clock,
+            DelayableExecutor mainExecutor,
+            @Named(SERVICE_CONNECTION) ObservableServiceConnection<T> serviceConnection,
+            @Named(MAX_RECONNECT_ATTEMPTS) int maxReconnectAttempts,
+            @Named(BASE_RECONNECT_DELAY_MS) int baseReconnectDelayMs,
+            @Named(MIN_CONNECTION_DURATION_MS) int minConnectionDurationMs,
+            @Named(OBSERVER) Observer observer) {
+        mSystemClock = clock;
+        mMainExecutor = mainExecutor;
+        mConnection = serviceConnection;
+        mObserver = observer;
+
+        mMaxReconnectAttempts = maxReconnectAttempts;
+        mBaseReconnectDelayMs = baseReconnectDelayMs;
+        mMinConnectionDuration = minConnectionDurationMs;
+    }
+
+    /**
+     * Begins the {@link PersistentConnectionManager} by connecting to the associated service.
+     */
+    public void start() {
+        mConnection.addCallback(mConnectionCallback);
+        mObserver.addCallback(mObserverCallback);
+        initiateConnectionAttempt();
+    }
+
+    /**
+     * Brings down the {@link PersistentConnectionManager}, disconnecting from the service.
+     */
+    public void stop() {
+        mConnection.removeCallback(mConnectionCallback);
+        mObserver.removeCallback(mObserverCallback);
+        mConnection.unbind();
+    }
+
+    private void initiateConnectionAttempt() {
+        // Reset attempts
+        mReconnectAttempts = 0;
+
+        // The first attempt is always a direct invocation rather than delayed.
+        mConnection.bind();
+    }
+
+    private void scheduleConnectionAttempt() {
+        // always clear cancelable if present.
+        if (mCurrentReconnectCancelable != null) {
+            mCurrentReconnectCancelable.run();
+            mCurrentReconnectCancelable = null;
+        }
+
+        if (mReconnectAttempts >= mMaxReconnectAttempts) {
+            if (DEBUG) {
+                Log.d(TAG, "exceeded max connection attempts.");
+            }
+            return;
+        }
+
+        final long reconnectDelayMs =
+                (long) Math.scalb(mBaseReconnectDelayMs, mReconnectAttempts);
+
+        if (DEBUG) {
+            Log.d(TAG,
+                    "scheduling connection attempt in " + reconnectDelayMs + "milliseconds");
+        }
+
+        mCurrentReconnectCancelable = mMainExecutor.executeDelayed(mConnectRunnable,
+                reconnectDelayMs);
+
+        mReconnectAttempts++;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/dagger/ObservableServiceModule.java b/packages/SystemUI/src/com/android/systemui/util/service/dagger/ObservableServiceModule.java
new file mode 100644
index 0000000..c62c957
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/service/dagger/ObservableServiceModule.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.systemui.util.service.dagger;
+
+import android.content.res.Resources;
+
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
+
+import javax.inject.Named;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Module containing components and parameters for
+ * {@link com.android.systemui.util.service.ObservableServiceConnection}
+ * and {@link com.android.systemui.util.service.PersistentConnectionManager}.
+ */
+@Module(subcomponents = {
+        PackageObserverComponent.class,
+})
+public class ObservableServiceModule {
+    public static final String MAX_RECONNECT_ATTEMPTS = "max_reconnect_attempts";
+    public static final String BASE_RECONNECT_DELAY_MS = "base_reconnect_attempts";
+    public static final String MIN_CONNECTION_DURATION_MS = "min_connection_duration_ms";
+    public static final String SERVICE_CONNECTION = "service_connection";
+    public static final String OBSERVER = "observer";
+
+    @Provides
+    @Named(MAX_RECONNECT_ATTEMPTS)
+    static int providesMaxReconnectAttempts(@Main Resources resources) {
+        return resources.getInteger(
+                R.integer.config_communalSourceMaxReconnectAttempts);
+    }
+
+    @Provides
+    @Named(BASE_RECONNECT_DELAY_MS)
+    static int provideBaseReconnectDelayMs(@Main Resources resources) {
+        return resources.getInteger(
+                R.integer.config_communalSourceReconnectBaseDelay);
+    }
+
+    @Provides
+    @Named(MIN_CONNECTION_DURATION_MS)
+    static int providesMinConnectionDuration(@Main Resources resources) {
+        return resources.getInteger(
+                R.integer.config_connectionMinDuration);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/dagger/PackageObserverComponent.java b/packages/SystemUI/src/com/android/systemui/util/service/dagger/PackageObserverComponent.java
new file mode 100644
index 0000000..8ee39b3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/service/dagger/PackageObserverComponent.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.util.service.dagger;
+
+import android.content.ComponentName;
+
+import com.android.systemui.util.service.PackageObserver;
+
+import dagger.BindsInstance;
+import dagger.Subcomponent;
+
+/**
+ * Generates a scoped {@link PackageObserver}.
+ */
+@Subcomponent
+public interface PackageObserverComponent {
+    /**
+     * Generates a {@link PackageObserverComponent} instance.
+     */
+    @Subcomponent.Factory
+    interface Factory {
+        PackageObserverComponent create(@BindsInstance ComponentName component);
+    }
+
+    /**
+     * Creates a {@link PackageObserver}.
+     */
+    PackageObserver getPackageObserver();
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt
index 164f83d..6c1f008 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt
@@ -20,23 +20,21 @@
 import android.view.View
 import android.view.ViewGroup
 import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardUnfoldTransition.Companion.LEFT
-import com.android.keyguard.KeyguardUnfoldTransition.Companion.RIGHT
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
 import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
 import com.android.systemui.util.mockito.capture
-import org.junit.Assert.assertEquals
+import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.Captor
 import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
 
 /**
  * Translates items away/towards the hinge when the device is opened/closed. This is controlled by
@@ -46,14 +44,11 @@
 @RunWith(AndroidTestingRunner::class)
 class KeyguardUnfoldTransitionTest : SysuiTestCase() {
 
-    @Mock
-    private lateinit var progressProvider: NaturalRotationUnfoldProgressProvider
+    @Mock private lateinit var progressProvider: NaturalRotationUnfoldProgressProvider
 
-    @Captor
-    private lateinit var progressListenerCaptor: ArgumentCaptor<TransitionProgressListener>
+    @Captor private lateinit var progressListenerCaptor: ArgumentCaptor<TransitionProgressListener>
 
-    @Mock
-    private lateinit var parent: ViewGroup
+    @Mock private lateinit var parent: ViewGroup
 
     private lateinit var keyguardUnfoldTransition: KeyguardUnfoldTransition
     private lateinit var progressListener: TransitionProgressListener
@@ -63,87 +58,35 @@
     fun setup() {
         MockitoAnnotations.initMocks(this)
 
-        xTranslationMax = context.resources.getDimensionPixelSize(
-            R.dimen.keyguard_unfold_translation_x).toFloat()
+        xTranslationMax =
+            context.resources.getDimensionPixelSize(R.dimen.keyguard_unfold_translation_x).toFloat()
 
-        keyguardUnfoldTransition = KeyguardUnfoldTransition(
-            getContext(),
-            progressProvider
-        )
-
-        verify(progressProvider).addCallback(capture(progressListenerCaptor))
-        progressListener = progressListenerCaptor.value
+        keyguardUnfoldTransition = KeyguardUnfoldTransition(context, progressProvider)
 
         keyguardUnfoldTransition.setup(parent)
         keyguardUnfoldTransition.statusViewCentered = false
-    }
 
-    @Test
-    fun onTransition_noMatchingIds() {
-        // GIVEN no views matching any ids
-        // WHEN the transition starts
-        progressListener.onTransitionStarted()
-        progressListener.onTransitionProgress(.1f)
-
-        // THEN nothing... no exceptions
-    }
-
-    @Test
-    fun onTransition_oneMovesLeft() {
-        // GIVEN one view with a matching id
-        val view = View(getContext())
-        `when`(parent.findViewById<View>(R.id.keyguard_status_area)).thenReturn(view)
-
-        moveAndValidate(listOf(view to LEFT))
-    }
-
-    @Test
-    fun onTransition_oneMovesLeftAndOneMovesRightMultipleTimes() {
-        // GIVEN two views with a matching id
-        val leftView = View(getContext())
-        val rightView = View(getContext())
-        `when`(parent.findViewById<View>(R.id.keyguard_status_area)).thenReturn(leftView)
-        `when`(parent.findViewById<View>(R.id.notification_stack_scroller)).thenReturn(rightView)
-
-        moveAndValidate(listOf(leftView to LEFT, rightView to RIGHT))
-        moveAndValidate(listOf(leftView to LEFT, rightView to RIGHT))
+        verify(progressProvider).addCallback(capture(progressListenerCaptor))
+        progressListener = progressListenerCaptor.value
     }
 
     @Test
     fun onTransition_centeredViewDoesNotMove() {
         keyguardUnfoldTransition.statusViewCentered = true
 
-        val view = View(getContext())
+        val view = View(context)
         `when`(parent.findViewById<View>(R.id.lockscreen_clock_view_large)).thenReturn(view)
 
-        moveAndValidate(listOf(view to 0))
-    }
-
-    private fun moveAndValidate(list: List<Pair<View, Int>>) {
-        // Compare values as ints because -0f != 0f
-
-        // WHEN the transition starts
         progressListener.onTransitionStarted()
+        assertThat(view.translationX).isZero()
+
         progressListener.onTransitionProgress(0f)
+        assertThat(view.translationX).isZero()
 
-        list.forEach { (view, direction) ->
-            assertEquals((-xTranslationMax * direction).toInt(), view.getTranslationX().toInt())
-        }
+        progressListener.onTransitionProgress(0.5f)
+        assertThat(view.translationX).isZero()
 
-        // WHEN the transition progresses, translation is updated
-        progressListener.onTransitionProgress(.5f)
-        list.forEach { (view, direction) ->
-            assertEquals(
-                (-xTranslationMax / 2f * direction).toInt(),
-                view.getTranslationX().toInt()
-            )
-        }
-
-        // WHEN the transition ends, translation is completed
-        progressListener.onTransitionProgress(1f)
         progressListener.onTransitionFinished()
-        list.forEach { (view, _) ->
-            assertEquals(0, view.getTranslationX().toInt())
-        }
+        assertThat(view.translationX).isZero()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
new file mode 100644
index 0000000..3231415
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
@@ -0,0 +1,117 @@
+package com.android.systemui.shared.animation
+
+import android.testing.AndroidTestingRunner
+import android.view.View
+import android.view.ViewGroup
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class UnfoldConstantTranslateAnimatorTest : SysuiTestCase() {
+
+    @Mock private lateinit var progressProvider: UnfoldTransitionProgressProvider
+
+    @Mock private lateinit var parent: ViewGroup
+
+    @Captor private lateinit var progressListenerCaptor: ArgumentCaptor<TransitionProgressListener>
+
+    private lateinit var animator: UnfoldConstantTranslateAnimator
+    private lateinit var progressListener: TransitionProgressListener
+
+    private val viewsIdToRegister =
+        setOf(
+            ViewIdToTranslate(LEFT_VIEW_ID, Direction.LEFT),
+            ViewIdToTranslate(RIGHT_VIEW_ID, Direction.RIGHT))
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        animator =
+            UnfoldConstantTranslateAnimator(viewsIdToRegister, progressProvider)
+
+        animator.init(parent, MAX_TRANSLATION)
+
+        verify(progressProvider).addCallback(progressListenerCaptor.capture())
+        progressListener = progressListenerCaptor.value
+    }
+
+    @Test
+    fun onTransition_noMatchingIds() {
+        // GIVEN no views matching any ids
+        // WHEN the transition starts
+        progressListener.onTransitionStarted()
+        progressListener.onTransitionProgress(.1f)
+
+        // THEN nothing... no exceptions
+    }
+
+    @Test
+    fun onTransition_oneMovesLeft() {
+        // GIVEN one view with a matching id
+        val view = View(context)
+        whenever(parent.findViewById<View>(LEFT_VIEW_ID)).thenReturn(view)
+
+        moveAndValidate(listOf(view to LEFT))
+    }
+
+    @Test
+    fun onTransition_oneMovesLeftAndOneMovesRightMultipleTimes() {
+        // GIVEN two views with a matching id
+        val leftView = View(context)
+        val rightView = View(context)
+        whenever(parent.findViewById<View>(LEFT_VIEW_ID)).thenReturn(leftView)
+        whenever(parent.findViewById<View>(RIGHT_VIEW_ID)).thenReturn(rightView)
+
+        moveAndValidate(listOf(leftView to LEFT, rightView to RIGHT))
+        moveAndValidate(listOf(leftView to LEFT, rightView to RIGHT))
+    }
+
+    private fun moveAndValidate(list: List<Pair<View, Int>>) {
+        // Compare values as ints because -0f != 0f
+
+        // WHEN the transition starts
+        progressListener.onTransitionStarted()
+        progressListener.onTransitionProgress(0f)
+
+        list.forEach { (view, direction) ->
+            assertEquals((-MAX_TRANSLATION * direction).toInt(), view.translationX.toInt())
+        }
+
+        // WHEN the transition progresses, translation is updated
+        progressListener.onTransitionProgress(.5f)
+        list.forEach { (view, direction) ->
+            assertEquals((-MAX_TRANSLATION / 2f * direction).toInt(), view.translationX.toInt())
+        }
+
+        // WHEN the transition ends, translation is completed
+        progressListener.onTransitionProgress(1f)
+        progressListener.onTransitionFinished()
+        list.forEach { (view, _) -> assertEquals(0, view.translationX.toInt()) }
+    }
+
+    companion object {
+        private val LEFT = Direction.LEFT.multiplier.toInt()
+        private val RIGHT = Direction.RIGHT.multiplier.toInt()
+
+        private const val MAX_TRANSLATION = 42f
+
+        private const val LEFT_VIEW_ID = 1
+        private const val RIGHT_VIEW_ID = 2
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
index 83f1d87..7fafb24 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -35,6 +35,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
@@ -56,6 +57,7 @@
 import com.android.systemui.statusbar.notification.stack.ForegroundServiceSectionController;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.wm.shell.bubbles.Bubbles;
 
 import com.google.android.collect.Lists;
@@ -123,7 +125,9 @@
                 mock(DynamicChildBindController.class),
                 mock(LowPriorityInflationHelper.class),
                 mock(AssistantFeedbackController.class),
-                mNotifPipelineFlags);
+                mNotifPipelineFlags,
+                mock(KeyguardUpdateMonitor.class),
+                mock(KeyguardStateController.class));
         mViewHierarchyManager.setUpWithPresenter(mPresenter, mStackController, mListContainer);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
index 5fd4174..3f84c16 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
@@ -19,8 +19,11 @@
 import android.os.UserHandle
 import android.service.notification.StatusBarNotification
 import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.notification.DynamicPrivacyController
 import com.android.systemui.statusbar.notification.collection.ListEntry
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
@@ -28,9 +31,12 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
+import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import org.junit.Test
+import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
 
@@ -40,9 +46,13 @@
     val dynamicPrivacyController: DynamicPrivacyController = mock()
     val lockscreenUserManager: NotificationLockscreenUserManager = mock()
     val pipeline: NotifPipeline = mock()
+    val keyguardUpdateMonitor: KeyguardUpdateMonitor = mock()
+    val statusBarStateController: StatusBarStateController = mock()
+    val keyguardStateController: KeyguardStateController = mock()
 
     val coordinator: SensitiveContentCoordinator = SensitiveContentCoordinatorModule
-            .provideCoordinator(dynamicPrivacyController, lockscreenUserManager)
+            .provideCoordinator(dynamicPrivacyController, lockscreenUserManager,
+            keyguardUpdateMonitor, statusBarStateController, keyguardStateController)
 
     @Test
     fun onDynamicPrivacyChanged_invokeInvalidationListener() {
@@ -190,6 +200,28 @@
         verify(entry.representativeEntry!!).setSensitive(true, true)
     }
 
+    @Test
+    fun onBeforeRenderList_deviceDynamicallyUnlocked_deviceBiometricBypassingLockScreen() {
+        coordinator.attach(pipeline)
+        val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
+            verify(pipeline).addOnBeforeRenderListListener(capture())
+        }
+
+        whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+        whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+        whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+        whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
+        whenever(statusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD)
+        whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing(any()))
+                .thenReturn(true)
+
+        val entry = fakeNotification(2, true)
+
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+        verify(entry.representativeEntry!!, never()).setSensitive(any(), any())
+    }
+
     private fun fakeNotification(notifUserId: Int, needsRedaction: Boolean): ListEntry {
         val mockUserHandle = mock<UserHandle>().apply {
             whenever(identifier).thenReturn(notifUserId)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java
index 15ff555..ab71264 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.collection.render;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 
 import android.content.Context;
 import android.testing.AndroidTestingRunner;
@@ -138,7 +139,7 @@
     }
 
     @Test
-    public void testRemovedGroupsAreKeptTogether() {
+    public void testRemovedGroupsAreBrokenApart() {
         // GIVEN a preexisting tree with a group
         applySpecAndCheck(
                 node(mController1),
@@ -154,10 +155,10 @@
                 node(mController1)
         );
 
-        // THEN the group children are still attached to their parent
-        assertEquals(mController2.getView(), mController3.getView().getParent());
-        assertEquals(mController2.getView(), mController4.getView().getParent());
-        assertEquals(mController2.getView(), mController5.getView().getParent());
+        // THEN the group children are no longer attached to their parent
+        assertNull(mController3.getView().getParent());
+        assertNull(mController4.getView().getParent());
+        assertNull(mController5.getView().getParent());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java
index 5d7b154..d002cebe5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java
@@ -250,15 +250,17 @@
             @Notification.GroupAlertBehavior int priorityGroupAlert,
             @Notification.GroupAlertBehavior int siblingGroupAlert,
             boolean expectAlertOverride) {
+        long when = 10000;
         // Create entries in an order so that the priority entry can be deemed the newest child.
         NotificationEntry[] siblings = new NotificationEntry[numSiblings];
         for (int i = 0; i < numSiblings; i++) {
-            siblings[i] = mGroupTestHelper.createChildNotification(siblingGroupAlert, i, "sibling");
+            siblings[i] = mGroupTestHelper
+                    .createChildNotification(siblingGroupAlert, i, "sibling", ++when);
         }
         NotificationEntry priorityEntry =
-                mGroupTestHelper.createChildNotification(priorityGroupAlert, 0, "priority");
+                mGroupTestHelper.createChildNotification(priorityGroupAlert, 0, "priority", ++when);
         NotificationEntry summaryEntry =
-                mGroupTestHelper.createSummaryNotification(summaryGroupAlert, 0, "summary");
+                mGroupTestHelper.createSummaryNotification(summaryGroupAlert, 0, "summary", ++when);
 
         // The priority entry is an important conversation.
         when(mPeopleNotificationIdentifier.getPeopleNotificationType(eq(priorityEntry)))
@@ -322,17 +324,19 @@
 
     @Test
     public void testAlertOverrideWhenUpdatingSummaryAtEnd() {
+        long when = 10000;
         int numSiblings = 2;
         int groupAlert = Notification.GROUP_ALERT_SUMMARY;
         // Create entries in an order so that the priority entry can be deemed the newest child.
         NotificationEntry[] siblings = new NotificationEntry[numSiblings];
         for (int i = 0; i < numSiblings; i++) {
-            siblings[i] = mGroupTestHelper.createChildNotification(groupAlert, i, "sibling");
+            siblings[i] =
+                    mGroupTestHelper.createChildNotification(groupAlert, i, "sibling", ++when);
         }
         NotificationEntry priorityEntry =
-                mGroupTestHelper.createChildNotification(groupAlert, 0, "priority");
+                mGroupTestHelper.createChildNotification(groupAlert, 0, "priority", ++when);
         NotificationEntry summaryEntry =
-                mGroupTestHelper.createSummaryNotification(groupAlert, 0, "summary");
+                mGroupTestHelper.createSummaryNotification(groupAlert, 0, "summary", ++when);
 
         // The priority entry is an important conversation.
         when(mPeopleNotificationIdentifier.getPeopleNotificationType(eq(priorityEntry)))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
index fa2a906..9a7e702 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
@@ -400,4 +400,16 @@
 
         assertEquals(fgUserName, userSwitcherController.currentUserName)
     }
+
+    @Test
+    fun isSystemUser_currentUserIsSystemUser_shouldReturnTrue() {
+        `when`(userTracker.userId).thenReturn(UserHandle.USER_SYSTEM)
+        assertEquals(true, userSwitcherController.isSystemUser)
+    }
+
+    @Test
+    fun isSystemUser_currentUserIsNotSystemUser_shouldReturnFalse() {
+        `when`(userTracker.userId).thenReturn(1)
+        assertEquals(false, userSwitcherController.isSystemUser)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/PackageObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/service/PackageObserverTest.java
new file mode 100644
index 0000000..a2fd288
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/service/PackageObserverTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.util.service;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.verify;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class PackageObserverTest extends SysuiTestCase {
+    @Mock
+    Context mContext;
+
+    @Mock
+    Observer.Callback mCallback;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testChange() {
+        final PackageObserver observer = new PackageObserver(mContext,
+                ComponentName.unflattenFromString("com.foo.bar/baz"));
+        final ArgumentCaptor<BroadcastReceiver> receiverCapture =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+
+        observer.addCallback(mCallback);
+
+        // Verify broadcast receiver registered.
+        verify(mContext).registerReceiver(receiverCapture.capture(), any(), anyInt());
+
+        // Simulate package change.
+        receiverCapture.getValue().onReceive(mContext, new Intent());
+
+        // Check that callback was informed.
+        verify(mCallback).onSourceChanged();
+
+        observer.removeCallback(mCallback);
+
+        // Make sure receiver is unregistered on last callback removal
+        verify(mContext).unregisterReceiver(receiverCapture.getValue());
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java
new file mode 100644
index 0000000..53d4a96
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.service;
+
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class PersistentConnectionManagerTest extends SysuiTestCase {
+    private static final int MAX_RETRIES = 5;
+    private static final int RETRY_DELAY_MS = 1000;
+    private static final int CONNECTION_MIN_DURATION_MS = 5000;
+
+    private FakeSystemClock mFakeClock = new FakeSystemClock();
+    private FakeExecutor mFakeExecutor = new FakeExecutor(mFakeClock);
+
+    @Mock
+    private ObservableServiceConnection<Proxy> mConnection;
+
+    @Mock
+    private Observer mObserver;
+
+    private static class Proxy {
+    }
+
+    private PersistentConnectionManager<Proxy> mConnectionManager;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+
+        mConnectionManager = new PersistentConnectionManager<>(
+                mFakeClock,
+                mFakeExecutor,
+                mConnection,
+                MAX_RETRIES,
+                RETRY_DELAY_MS,
+                CONNECTION_MIN_DURATION_MS,
+                mObserver);
+    }
+
+    private ObservableServiceConnection.Callback<Proxy> captureCallbackAndSend(
+            ObservableServiceConnection<Proxy> mConnection, Proxy proxy) {
+        ArgumentCaptor<ObservableServiceConnection.Callback<Proxy>> connectionCallbackCaptor =
+                ArgumentCaptor.forClass(ObservableServiceConnection.Callback.class);
+
+        verify(mConnection).addCallback(connectionCallbackCaptor.capture());
+        verify(mConnection).bind();
+        Mockito.clearInvocations(mConnection);
+
+        final ObservableServiceConnection.Callback callback = connectionCallbackCaptor.getValue();
+        if (proxy != null) {
+            callback.onConnected(mConnection, proxy);
+        } else {
+            callback.onDisconnected(mConnection, 0);
+        }
+
+        return callback;
+    }
+
+    /**
+     * Validates initial connection.
+     */
+    @Test
+    public void testConnect() {
+        mConnectionManager.start();
+        captureCallbackAndSend(mConnection, Mockito.mock(Proxy.class));
+    }
+
+    /**
+     * Ensures reconnection on disconnect.
+     */
+    @Test
+    public void testRetryOnBindFailure() {
+        mConnectionManager.start();
+        ArgumentCaptor<ObservableServiceConnection.Callback<Proxy>> connectionCallbackCaptor =
+                ArgumentCaptor.forClass(ObservableServiceConnection.Callback.class);
+
+        verify(mConnection).addCallback(connectionCallbackCaptor.capture());
+
+        // Verify attempts happen. Note that we account for the retries plus initial attempt, which
+        // is not scheduled.
+        for (int attemptCount = 0; attemptCount < MAX_RETRIES + 1; attemptCount++) {
+            verify(mConnection).bind();
+            Mockito.clearInvocations(mConnection);
+            connectionCallbackCaptor.getValue().onDisconnected(mConnection, 0);
+            mFakeExecutor.advanceClockToNext();
+            mFakeExecutor.runAllReady();
+        }
+    }
+
+    /**
+     * Ensures rebind on package change.
+     */
+    @Test
+    public void testAttemptOnPackageChange() {
+        mConnectionManager.start();
+        verify(mConnection).bind();
+        ArgumentCaptor<Observer.Callback> callbackCaptor =
+                ArgumentCaptor.forClass(Observer.Callback.class);
+        captureCallbackAndSend(mConnection, Mockito.mock(Proxy.class));
+
+        verify(mObserver).addCallback(callbackCaptor.capture());
+
+        callbackCaptor.getValue().onSourceChanged();
+        verify(mConnection).bind();
+    }
+}
diff --git a/services/api/current.txt b/services/api/current.txt
index 50f0052..dcf7e64 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -39,6 +39,7 @@
 
   public interface ActivityManagerLocal {
     method public boolean canStartForegroundService(int, int, @NonNull String);
+    method public boolean startAndBindSupplementalProcessService(@NonNull android.content.Intent, @NonNull android.content.ServiceConnection, int) throws android.os.TransactionTooLargeException;
   }
 
 }
diff --git a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
index 0eb6b8d..627b0be 100644
--- a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
+++ b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
@@ -21,8 +21,6 @@
 import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
 import static android.bluetooth.BluetoothAdapter.EXTRA_PREVIOUS_STATE;
 import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
-import static android.bluetooth.BluetoothAdapter.STATE_BLE_ON;
-import static android.bluetooth.BluetoothAdapter.STATE_ON;
 import static android.bluetooth.BluetoothAdapter.nameForState;
 import static android.bluetooth.le.ScanCallback.SCAN_FAILED_ALREADY_STARTED;
 import static android.bluetooth.le.ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED;
@@ -156,7 +154,7 @@
     private void checkBleState() {
         enforceInitialized();
 
-        final boolean bleAvailable = isBleAvailable();
+        final boolean bleAvailable = mBtAdapter.isLeEnabled();
         if (DEBUG) {
             Log.i(TAG, "checkBleState() bleAvailable=" + bleAvailable);
         }
@@ -183,16 +181,6 @@
         }
     }
 
-    /**
-     * A duplicate of {@code BluetoothAdapter.getLeAccess()} method which has the package-private
-     * access level, so it's not accessible from here.
-     */
-    private boolean isBleAvailable() {
-        final int state = mBtAdapter.getLeState();
-        if (DEBUG) Log.d(TAG, "getLeAccess() state=" + nameForBtState(state));
-        return state == STATE_ON || state == STATE_BLE_ON;
-    }
-
     @MainThread
     private void startScan() {
         enforceInitialized();
diff --git a/services/core/java/com/android/server/DockObserver.java b/services/core/java/com/android/server/DockObserver.java
index e5a7b4e..8a6b54f 100644
--- a/services/core/java/com/android/server/DockObserver.java
+++ b/services/core/java/com/android/server/DockObserver.java
@@ -19,7 +19,6 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.PackageManager;
 import android.media.AudioManager;
 import android.media.Ringtone;
 import android.media.RingtoneManager;
@@ -32,15 +31,21 @@
 import android.os.UEventObserver;
 import android.os.UserHandle;
 import android.provider.Settings;
-import android.util.Log;
+import android.util.Pair;
 import android.util.Slog;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.DumpUtils;
+import com.android.server.ExtconUEventObserver.ExtconInfo;
 
 import java.io.FileDescriptor;
 import java.io.FileNotFoundException;
 import java.io.FileReader;
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 /**
  * DockObserver monitors for a docking station.
@@ -48,9 +53,6 @@
 final class DockObserver extends SystemService {
     private static final String TAG = "DockObserver";
 
-    private static final String DOCK_UEVENT_MATCH = "DEVPATH=/devices/virtual/switch/dock";
-    private static final String DOCK_STATE_PATH = "/sys/class/switch/dock/state";
-
     private static final int MSG_DOCK_STATE_CHANGED = 0;
 
     private final PowerManager mPowerManager;
@@ -69,6 +71,92 @@
 
     private final boolean mAllowTheaterModeWakeFromDock;
 
+    private final List<ExtconStateConfig> mExtconStateConfigs;
+
+    static final class ExtconStateProvider {
+        private final Map<String, String> mState;
+
+        ExtconStateProvider(Map<String, String> state) {
+            mState = state;
+        }
+
+        String getValue(String key) {
+            return mState.get(key);
+        }
+
+
+        static ExtconStateProvider fromString(String stateString) {
+            Map<String, String> states = new HashMap<>();
+            String[] lines = stateString.split("\n");
+            for (String line : lines) {
+                String[] fields = line.split("=");
+                if (fields.length == 2) {
+                    states.put(fields[0], fields[1]);
+                } else {
+                    Slog.e(TAG, "Invalid line: " + line);
+                }
+            }
+            return new ExtconStateProvider(states);
+        }
+
+        static ExtconStateProvider fromFile(String stateFilePath) {
+            char[] buffer = new char[1024];
+            try (FileReader file = new FileReader(stateFilePath)) {
+                int len = file.read(buffer, 0, 1024);
+                String stateString = (new String(buffer, 0, len)).trim();
+                return ExtconStateProvider.fromString(stateString);
+            } catch (FileNotFoundException e) {
+                Slog.w(TAG, "No state file found at: " + stateFilePath);
+                return new ExtconStateProvider(new HashMap<>());
+            } catch (Exception e) {
+                Slog.e(TAG, "" , e);
+                return new ExtconStateProvider(new HashMap<>());
+            }
+        }
+    }
+
+    /**
+     * Represents a mapping from extcon state to EXTRA_DOCK_STATE value. Each
+     * instance corresponds to an entry in config_dockExtconStateMapping.
+     */
+    private static final class ExtconStateConfig {
+
+        // The EXTRA_DOCK_STATE that will be used if the extcon key-value pairs match
+        public final int extraStateValue;
+
+        // A list of key-value pairs that must be present in the extcon state for a match
+        // to be considered. An empty list is considered a matching wildcard.
+        public final List<Pair<String, String>> keyValuePairs = new ArrayList<>();
+
+        ExtconStateConfig(int extraStateValue) {
+            this.extraStateValue = extraStateValue;
+        }
+    }
+
+    private static List<ExtconStateConfig> loadExtconStateConfigs(Context context) {
+        String[] rows = context.getResources().getStringArray(
+            com.android.internal.R.array.config_dockExtconStateMapping);
+        try {
+            ArrayList<ExtconStateConfig> configs = new ArrayList<>();
+            for (String row : rows) {
+                String[] rowFields = row.split(",");
+                ExtconStateConfig config = new ExtconStateConfig(Integer.parseInt(rowFields[0]));
+                for (int i = 1; i < rowFields.length; i++) {
+                    String[] keyValueFields = rowFields[i].split("=");
+                    if (keyValueFields.length != 2) {
+                        throw new IllegalArgumentException("Invalid key-value: " + rowFields[i]);
+                    }
+                    config.keyValuePairs.add(Pair.create(keyValueFields[0], keyValueFields[1]));
+                }
+                configs.add(config);
+            }
+            return configs;
+        } catch (IllegalArgumentException | ArrayIndexOutOfBoundsException e) {
+            Slog.e(TAG, "Could not parse extcon state config", e);
+            return new ArrayList<>();
+        }
+    }
+
     public DockObserver(Context context) {
         super(context);
 
@@ -77,9 +165,25 @@
         mAllowTheaterModeWakeFromDock = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_allowTheaterModeWakeFromDock);
 
-        init();  // set initial status
+        mExtconStateConfigs = loadExtconStateConfigs(context);
 
-        mObserver.startObserving(DOCK_UEVENT_MATCH);
+        List<ExtconInfo> infos = ExtconInfo.getExtconInfoForTypes(new String[] {
+                ExtconInfo.EXTCON_DOCK
+        });
+
+        if (!infos.isEmpty()) {
+            ExtconInfo info = infos.get(0);
+            Slog.i(TAG, "Found extcon info devPath: " + info.getDevicePath()
+                        + ", statePath: " + info.getStatePath());
+
+            // set initial status
+            setDockStateFromProviderLocked(ExtconStateProvider.fromFile(info.getStatePath()));
+            mPreviousDockState = mActualDockState;
+
+            mExtconUEventObserver.startObserving(info);
+        } else {
+            Slog.i(TAG, "No extcon dock device found in this kernel.");
+        }
     }
 
     @Override
@@ -101,26 +205,6 @@
         }
     }
 
-    private void init() {
-        synchronized (mLock) {
-            try {
-                char[] buffer = new char[1024];
-                FileReader file = new FileReader(DOCK_STATE_PATH);
-                try {
-                    int len = file.read(buffer, 0, 1024);
-                    setActualDockStateLocked(Integer.parseInt((new String(buffer, 0, len)).trim()));
-                    mPreviousDockState = mActualDockState;
-                } finally {
-                    file.close();
-                }
-            } catch (FileNotFoundException e) {
-                Slog.w(TAG, "This kernel does not have dock station support");
-            } catch (Exception e) {
-                Slog.e(TAG, "" , e);
-            }
-        }
-    }
-
     private void setActualDockStateLocked(int newState) {
         mActualDockState = newState;
         if (!mUpdatesStopped) {
@@ -234,19 +318,50 @@
         }
     };
 
-    private final UEventObserver mObserver = new UEventObserver() {
-        @Override
-        public void onUEvent(UEventObserver.UEvent event) {
-            if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                Slog.v(TAG, "Dock UEVENT: " + event.toString());
+    private int getDockedStateExtraValue(ExtconStateProvider state) {
+        for (ExtconStateConfig config : mExtconStateConfigs) {
+            boolean match = true;
+            for (Pair<String, String> keyValue : config.keyValuePairs) {
+                String stateValue = state.getValue(keyValue.first);
+                match = match && keyValue.second.equals(stateValue);
+                if (!match) {
+                    break;
+                }
             }
 
-            try {
-                synchronized (mLock) {
-                    setActualDockStateLocked(Integer.parseInt(event.get("SWITCH_STATE")));
+            if (match) {
+                return config.extraStateValue;
+            }
+        }
+
+        return Intent.EXTRA_DOCK_STATE_DESK;
+    }
+
+    @VisibleForTesting
+    void setDockStateFromProviderForTesting(ExtconStateProvider provider) {
+        synchronized (mLock) {
+            setDockStateFromProviderLocked(provider);
+        }
+    }
+
+    private void setDockStateFromProviderLocked(ExtconStateProvider provider) {
+        int state = Intent.EXTRA_DOCK_STATE_UNDOCKED;
+        if ("1".equals(provider.getValue("DOCK"))) {
+            state = getDockedStateExtraValue(provider);
+        }
+        setActualDockStateLocked(state);
+    }
+
+    private final ExtconUEventObserver mExtconUEventObserver = new ExtconUEventObserver() {
+        @Override
+        public void onUEvent(ExtconInfo extconInfo, UEventObserver.UEvent event) {
+            synchronized (mLock) {
+                String stateString = event.get("STATE");
+                if (stateString != null) {
+                    setDockStateFromProviderLocked(ExtconStateProvider.fromString(stateString));
+                } else {
+                    Slog.e(TAG, "Extcon event missing STATE: " + event);
                 }
-            } catch (NumberFormatException e) {
-                Slog.e(TAG, "Could not parse switch state from event " + event);
             }
         }
     };
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index b5c0a67..9353dd8 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2721,7 +2721,8 @@
 
     int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service,
             String resolvedType, final IServiceConnection connection, int flags,
-            String instanceName, String callingPackage, final int userId)
+            String instanceName, boolean isSupplementalProcessService, String callingPackage,
+            final int userId)
             throws TransactionTooLargeException {
         if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "bindService: " + service
                 + " type=" + resolvedType + " conn=" + connection.asBinder()
@@ -2805,10 +2806,9 @@
         final boolean isBindExternal = (flags & Context.BIND_EXTERNAL_SERVICE) != 0;
         final boolean allowInstant = (flags & Context.BIND_ALLOW_INSTANT) != 0;
 
-        ServiceLookupResult res =
-            retrieveServiceLocked(service, instanceName, resolvedType, callingPackage,
-                    callingPid, callingUid, userId, true,
-                    callerFg, isBindExternal, allowInstant);
+        ServiceLookupResult res = retrieveServiceLocked(service, instanceName,
+                isSupplementalProcessService, resolvedType, callingPackage, callingPid, callingUid,
+                userId, true, callerFg, isBindExternal, allowInstant);
         if (res == null) {
             return 0;
         }
@@ -3228,6 +3228,20 @@
             int callingPid, int callingUid, int userId,
             boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal,
             boolean allowInstant) {
+        return retrieveServiceLocked(service, instanceName, false, resolvedType, callingPackage,
+                callingPid, callingUid, userId, createIfNeeded, callingFromFg, isBindExternal,
+                allowInstant);
+    }
+
+    private ServiceLookupResult retrieveServiceLocked(Intent service,
+            String instanceName, boolean isSupplementalProcessService, String resolvedType,
+            String callingPackage, int callingPid, int callingUid, int userId,
+            boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal,
+            boolean allowInstant) {
+        if (isSupplementalProcessService && instanceName == null) {
+            throw new IllegalArgumentException("No instanceName provided for supplemental process");
+        }
+
         ServiceRecord r = null;
         if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "retrieveServiceLocked: " + service
                 + " type=" + resolvedType + " callingUid=" + callingUid);
@@ -3249,7 +3263,6 @@
         if (instanceName == null) {
             comp = service.getComponent();
         } else {
-            // This is for isolated services
             final ComponentName realComp = service.getComponent();
             if (realComp == null) {
                 throw new IllegalArgumentException("Can't use custom instance name '" + instanceName
@@ -3304,12 +3317,19 @@
                     return null;
                 }
                 if (instanceName != null
-                        && (sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) == 0) {
+                        && (sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) == 0
+                        && !isSupplementalProcessService) {
                     throw new IllegalArgumentException("Can't use instance name '" + instanceName
-                            + "' with non-isolated service '" + sInfo.name + "'");
+                            + "' with non-isolated non-supplemental service '" + sInfo.name + "'");
                 }
-                ComponentName className = new ComponentName(
-                        sInfo.applicationInfo.packageName, sInfo.name);
+                if (isSupplementalProcessService
+                        && (sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0) {
+                    throw new IllegalArgumentException("Service cannot be both supplemental and "
+                            + "isolated");
+                }
+
+                ComponentName className = new ComponentName(sInfo.applicationInfo.packageName,
+                                                            sInfo.name);
                 ComponentName name = comp != null ? comp : className;
                 if (!mAm.validateAssociationAllowedLocked(callingPackage, callingUid,
                         name.getPackageName(), sInfo.applicationInfo.uid)) {
@@ -3392,7 +3412,8 @@
                             = new Intent.FilterComparison(service.cloneFilter());
                     final ServiceRestarter res = new ServiceRestarter();
                     r = new ServiceRecord(mAm, className, name, definingPackageName,
-                            definingUid, filter, sInfo, callingFromFg, res);
+                            definingUid, filter, sInfo, callingFromFg, res,
+                            isSupplementalProcessService);
                     res.setService(r);
                     smap.mServicesByInstanceName.put(name, r);
                     smap.mServicesByIntent.put(filter, r);
diff --git a/services/core/java/com/android/server/am/ActivityManagerLocal.java b/services/core/java/com/android/server/am/ActivityManagerLocal.java
index 9a1bfdd..d9ee7d9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerLocal.java
+++ b/services/core/java/com/android/server/am/ActivityManagerLocal.java
@@ -18,6 +18,9 @@
 
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.TransactionTooLargeException;
 
 /**
  * Interface for in-process calls into
@@ -58,4 +61,24 @@
      * @hide
      */
     void tempAllowWhileInUsePermissionInFgs(int uid, long durationMs);
+
+    /**
+     * Starts a supplemental process service and binds to it. You can through the arguments here
+     * have the system bring up multiple concurrent processes hosting their own instance of that
+     * service. The <var>userAppUid</var> you provide here identifies the different instances - each
+     * unique uid is attributed to a supplemental process.
+     *
+     * @param service Identifies the supplemental process service to connect to. The Intent must
+     *        specify an explicit component name. This value cannot be null.
+     * @param conn Receives information as the service is started and stopped.
+     *        This must be a valid ServiceConnection object; it must not be null.
+     * @param userAppUid Uid of the app for which the supplemental process needs to be spawned.
+     * @return {@code true} if the system is in the process of bringing up a
+     *         service that your client has permission to bind to; {@code false}
+     *         if the system couldn't find the service or if your client doesn't
+     *         have permission to bind to it.
+     */
+    boolean startAndBindSupplementalProcessService(@NonNull Intent service,
+            @NonNull ServiceConnection conn, int userAppUid) throws TransactionTooLargeException;
+
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 902659c..442b9de 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -218,6 +218,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.LocusId;
+import android.content.ServiceConnection;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ActivityPresentationInfo;
 import android.content.pm.ApplicationInfo;
@@ -256,6 +257,7 @@
 import android.os.BugreportParams;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.ConditionVariable;
 import android.os.Debug;
 import android.os.DropBoxManager;
 import android.os.FactoryTest;
@@ -336,7 +338,7 @@
 import com.android.internal.app.SystemUserHomeActivity;
 import com.android.internal.app.procstats.ProcessState;
 import com.android.internal.app.procstats.ProcessStats;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.os.BackgroundThread;
@@ -4951,7 +4953,7 @@
             // This line is needed to CTS test for the correct exception handling
             // See b/138952436#comment36 for context
             Slog.i(TAG, "About to commit checkpoint");
-            IStorageManager storageManager = PackageHelper.getStorageManager();
+            IStorageManager storageManager = InstallLocationUtils.getStorageManager();
             storageManager.commitChanges();
         } catch (Exception e) {
             PowerManager pm = (PowerManager)
@@ -12314,13 +12316,25 @@
     public int bindService(IApplicationThread caller, IBinder token, Intent service,
             String resolvedType, IServiceConnection connection, int flags,
             String callingPackage, int userId) throws TransactionTooLargeException {
-        return bindIsolatedService(caller, token, service, resolvedType, connection, flags,
+        return bindServiceInstance(caller, token, service, resolvedType, connection, flags,
                 null, callingPackage, userId);
     }
 
-    public int bindIsolatedService(IApplicationThread caller, IBinder token, Intent service,
+    /**
+     * Binds to a service with a given instanceName, creating it if it does not already exist.
+     * If the instanceName field is not supplied, binding to the service occurs as usual.
+     */
+    public int bindServiceInstance(IApplicationThread caller, IBinder token, Intent service,
             String resolvedType, IServiceConnection connection, int flags, String instanceName,
             String callingPackage, int userId) throws TransactionTooLargeException {
+        return bindServiceInstance(caller, token, service, resolvedType, connection, flags,
+                instanceName, false, callingPackage, userId);
+    }
+
+    private int bindServiceInstance(IApplicationThread caller, IBinder token, Intent service,
+            String resolvedType, IServiceConnection connection, int flags, String instanceName,
+            boolean isSupplementalProcessService, String callingPackage, int userId)
+            throws TransactionTooLargeException {
         enforceNotIsolatedCaller("bindService");
 
         // Refuse possible leaked file descriptors
@@ -12332,6 +12346,10 @@
             throw new IllegalArgumentException("callingPackage cannot be null");
         }
 
+        if (isSupplementalProcessService && instanceName == null) {
+            throw new IllegalArgumentException("No instance name provided for isolated process");
+        }
+
         // Ensure that instanceName, which is caller provided, does not contain
         // unusual characters.
         if (instanceName != null) {
@@ -12345,8 +12363,8 @@
         }
 
         synchronized(this) {
-            return mServices.bindServiceLocked(caller, token, service,
-                    resolvedType, connection, flags, instanceName, callingPackage, userId);
+            return mServices.bindServiceLocked(caller, token, service, resolvedType, connection,
+                    flags, instanceName, isSupplementalProcessService, callingPackage, userId);
         }
     }
 
@@ -15478,6 +15496,62 @@
         }
     }
 
+    /**
+     * Dump the resources structure for the given process
+     *
+     * @param process The process to dump resource info for
+     * @param fd The FileDescriptor to dump it into
+     * @throws RemoteException
+     */
+    public boolean dumpResources(String process, ParcelFileDescriptor fd, RemoteCallback callback)
+            throws RemoteException {
+        synchronized (this) {
+            ProcessRecord proc = findProcessLOSP(process, UserHandle.USER_CURRENT, "dumpResources");
+            IApplicationThread thread;
+            if (proc == null || (thread = proc.getThread()) == null) {
+                throw new IllegalArgumentException("Unknown process: " + process);
+            }
+            thread.dumpResources(fd, callback);
+            return true;
+        }
+    }
+
+    /**
+     * Dump the resources structure for all processes
+     *
+     * @param fd The FileDescriptor to dump it into
+     * @throws RemoteException
+     */
+    public void dumpAllResources(ParcelFileDescriptor fd, PrintWriter pw) throws RemoteException {
+        synchronized (mProcLock) {
+            mProcessList.forEachLruProcessesLOSP(true, app -> {
+                ConditionVariable lock = new ConditionVariable();
+                RemoteCallback
+                        finishCallback = new RemoteCallback(result -> lock.open(), null);
+
+                pw.println(String.format("------ DUMP RESOURCES %s (%s)  ------",
+                        app.processName,
+                        app.info.packageName));
+                pw.flush();
+                try {
+                    app.getThread().dumpResources(fd.dup(), finishCallback);
+                    lock.block(2000);
+                } catch (Exception e) {
+                    pw.println(String.format(
+                            "------ EXCEPTION DUMPING RESOURCES for %s (%s): %s ------",
+                            app.processName,
+                            app.info.packageName,
+                            e.getMessage()));
+                    pw.flush();
+                }
+                pw.println(String.format("------ END DUMP RESOURCES %s (%s)  ------",
+                        app.processName,
+                        app.info.packageName));
+                pw.flush();
+            });
+        }
+    }
+
     @Override
     public void setDumpHeapDebugLimit(String processName, int uid, long maxMemSize,
             String reportPackage) {
@@ -15823,6 +15897,34 @@
         }
 
         @Override
+        public boolean startAndBindSupplementalProcessService(Intent service,
+                ServiceConnection conn, int userAppUid) throws TransactionTooLargeException {
+            if (service == null) {
+                throw new IllegalArgumentException("intent is null");
+            }
+            if (conn == null) {
+                throw new IllegalArgumentException("connection is null");
+            }
+            if (service.getComponent() == null) {
+                throw new IllegalArgumentException("service must specify explicit component");
+            }
+            if (!UserHandle.isApp(userAppUid)) {
+                throw new IllegalArgumentException("uid is not within application range");
+            }
+
+            Handler handler = mContext.getMainThreadHandler();
+            int flags = Context.BIND_AUTO_CREATE;
+
+            final IServiceConnection sd = mContext.getServiceDispatcher(conn, handler, flags);
+            service.prepareToLeaveProcess(mContext);
+            return ActivityManagerService.this.bindServiceInstance(
+                        mContext.getIApplicationThread(), mContext.getActivityToken(), service,
+                        service.resolveTypeIfNeeded(mContext.getContentResolver()), sd, flags,
+                        Integer.toString(userAppUid), /*isSupplementalProcessService*/ true,
+                        mContext.getOpPackageName(), UserHandle.getUserId(userAppUid)) != 0;
+        }
+
+        @Override
         public void onUserRemoved(@UserIdInt int userId) {
             // Clean up any ActivityTaskManager state (by telling it the user is stopped)
             mAtmInternal.onUserStopped(userId);
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 24e7ba4..da78e2d 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -570,6 +570,14 @@
             ComponentName instanceName, String definingPackageName, int definingUid,
             Intent.FilterComparison intent, ServiceInfo sInfo, boolean callerIsFg,
             Runnable restarter) {
+        this(ams, name, instanceName, definingPackageName, definingUid, intent, sInfo, callerIsFg,
+                restarter, false);
+    }
+
+    ServiceRecord(ActivityManagerService ams, ComponentName name,
+            ComponentName instanceName, String definingPackageName, int definingUid,
+            Intent.FilterComparison intent, ServiceInfo sInfo, boolean callerIsFg,
+            Runnable restarter, boolean isSupplementalProcessService) {
         this.ams = ams;
         this.name = name;
         this.instanceName = instanceName;
@@ -580,7 +588,8 @@
         serviceInfo = sInfo;
         appInfo = sInfo.applicationInfo;
         packageName = sInfo.applicationInfo.packageName;
-        if ((sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0) {
+        if ((sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0
+                || isSupplementalProcessService) {
             processName = sInfo.processName + ":" + instanceName.getClassName();
         } else {
             processName = sInfo.processName;
diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
index 9136219..af1dd33 100644
--- a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
+++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
@@ -98,7 +98,10 @@
     private final IGameServiceController mGameServiceController =
             new IGameServiceController.Stub() {
                 @Override
+                @RequiresPermission(Manifest.permission.MANAGE_GAME_ACTIVITY)
                 public void createGameSession(int taskId) {
+                    mContext.enforceCallingPermission(Manifest.permission.MANAGE_GAME_ACTIVITY,
+                            "createGameSession()");
                     mBackgroundExecutor.execute(() -> {
                         GameServiceProviderInstanceImpl.this.createGameSession(taskId);
                     });
@@ -116,9 +119,10 @@
                     });
                 }
 
-                @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)
+                @Override
+                @RequiresPermission(Manifest.permission.MANAGE_GAME_ACTIVITY)
                 public void restartGame(int taskId) {
-                    mContext.enforceCallingPermission(Manifest.permission.FORCE_STOP_PACKAGES,
+                    mContext.enforceCallingPermission(Manifest.permission.MANAGE_GAME_ACTIVITY,
                             "restartGame()");
                     mBackgroundExecutor.execute(() -> {
                         GameServiceProviderInstanceImpl.this.restartGame(taskId);
@@ -372,12 +376,12 @@
                         }, mBackgroundExecutor);
 
         AndroidFuture<Void> unusedPostCreateGameSessionFuture =
-                mGameSessionServiceConnector.post(gameService -> {
+                mGameSessionServiceConnector.post(gameSessionService -> {
                     CreateGameSessionRequest createGameSessionRequest =
                             new CreateGameSessionRequest(
                                     taskId,
                                     existingGameSessionRecord.getComponentName().getPackageName());
-                    gameService.create(
+                    gameSessionService.create(
                             mGameSessionController,
                             createGameSessionRequest,
                             gameSessionViewHostConfiguration,
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index 0da6a1b..79705a3 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -53,6 +53,7 @@
 import android.hardware.biometrics.IBiometricServiceReceiver;
 import android.hardware.biometrics.IBiometricSysuiReceiver;
 import android.hardware.biometrics.PromptInfo;
+import android.hardware.biometrics.common.OperationContext;
 import android.hardware.face.FaceManager;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -64,6 +65,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.biometrics.log.BiometricFrameworkStatsLogger;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -681,16 +683,18 @@
                         + ", Latency: " + latency);
             }
 
-            FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_AUTHENTICATED,
+            final OperationContext operationContext = new OperationContext();
+            operationContext.isCrypto = isCrypto();
+            BiometricFrameworkStatsLogger.getInstance().authenticate(
+                    operationContext,
                     statsModality(),
-                    mUserId,
-                    isCrypto(),
+                    BiometricsProtoEnums.ACTION_UNKNOWN,
                     BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
-                    mPreAuthInfo.confirmationRequested,
-                    FrameworkStatsLog.BIOMETRIC_AUTHENTICATED__STATE__CONFIRMED,
-                    latency,
                     mDebugEnabled,
-                    -1 /* sensorId */,
+                    latency,
+                    FrameworkStatsLog.BIOMETRIC_AUTHENTICATED__STATE__CONFIRMED,
+                    mPreAuthInfo.confirmationRequested,
+                    mUserId,
                     -1f /* ambientLightLux */);
         } else {
             final long latency = System.currentTimeMillis() - mStartTimeMs;
@@ -711,17 +715,18 @@
                         + ", Latency: " + latency);
             }
             // Auth canceled
-            FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ERROR_OCCURRED,
+            final OperationContext operationContext = new OperationContext();
+            operationContext.isCrypto = isCrypto();
+            BiometricFrameworkStatsLogger.getInstance().error(
+                    operationContext,
                     statsModality(),
-                    mUserId,
-                    isCrypto(),
                     BiometricsProtoEnums.ACTION_AUTHENTICATE,
                     BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
-                    error,
-                    0 /* vendorCode */,
                     mDebugEnabled,
                     latency,
-                    -1 /* sensorId */);
+                    error,
+                    0 /* vendorCode */,
+                    mUserId);
         }
     }
 
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContext.java b/services/core/java/com/android/server/biometrics/log/BiometricContext.java
index 8d28298..c5e266f 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricContext.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricContext.java
@@ -17,6 +17,8 @@
 package com.android.server.biometrics.log;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
 import android.hardware.biometrics.common.OperationContext;
 
 import java.util.function.Consumer;
@@ -26,11 +28,21 @@
  * logging or optimizations.
  */
 public interface BiometricContext {
-    /** Gets the context source. */
-    static BiometricContext getInstance() {
-        return BiometricContextProvider.sInstance.get();
+    /** Gets the context source from the system context. */
+    static BiometricContext getInstance(@NonNull Context context) {
+        return BiometricContextProvider.defaultProvider(context);
     }
 
+    /** Update the given context with the most recent values and return it. */
+    OperationContext updateContext(@NonNull OperationContext operationContext,
+            boolean isCryptoOperation);
+
+    /** The session id for keyguard entry, if active, or null. */
+    @Nullable Integer getKeyguardEntrySessionId();
+
+    /** The session id for biometric prompt usage, if active, or null. */
+    @Nullable Integer getBiometricPromptSessionId();
+
     /** If the display is in AOD. */
     boolean isAoD();
 
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
index 65e9e3d..70acaff 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
@@ -18,16 +18,22 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.StatusBarManager;
 import android.content.Context;
 import android.hardware.biometrics.IBiometricContextListener;
 import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.common.OperationReason;
+import android.hardware.display.AmbientDisplayConfiguration;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.util.Singleton;
+import android.os.ServiceManager.ServiceNotFoundException;
+import android.os.UserHandle;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.InstanceId;
+import com.android.internal.statusbar.ISessionListener;
 import com.android.internal.statusbar.IStatusBarService;
 
 import java.util.Map;
@@ -41,22 +47,41 @@
 
     private static final String TAG = "BiometricContextProvider";
 
-    static final Singleton<BiometricContextProvider> sInstance =
-            new Singleton<BiometricContextProvider>() {
-                @Override
-                protected BiometricContextProvider create() {
-                    return new BiometricContextProvider(IStatusBarService.Stub.asInterface(
-                            ServiceManager.getService(
+    private static final int SESSION_TYPES =
+            StatusBarManager.SESSION_KEYGUARD | StatusBarManager.SESSION_BIOMETRIC_PROMPT;
+
+    private static BiometricContextProvider sInstance;
+
+    static BiometricContextProvider defaultProvider(@NonNull Context context) {
+        synchronized (BiometricContextProvider.class) {
+            if (sInstance == null) {
+                try {
+                    sInstance = new BiometricContextProvider(
+                            new AmbientDisplayConfiguration(context),
+                            IStatusBarService.Stub.asInterface(ServiceManager.getServiceOrThrow(
                                     Context.STATUS_BAR_SERVICE)), null /* handler */);
+                } catch (ServiceNotFoundException e) {
+                    throw new IllegalStateException("Failed to find required service", e);
                 }
-            };
+            }
+        }
+        return sInstance;
+    }
 
     @NonNull
     private final Map<OperationContext, Consumer<OperationContext>> mSubscribers =
             new ConcurrentHashMap<>();
 
+    @Nullable
+    private final Map<Integer, InstanceId> mSession = new ConcurrentHashMap<>();
+
+    private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
+    private boolean mIsDozing = false;
+
     @VisibleForTesting
-    BiometricContextProvider(@NonNull IStatusBarService service, @Nullable Handler handler) {
+    BiometricContextProvider(@NonNull AmbientDisplayConfiguration ambientDisplayConfiguration,
+            @NonNull IStatusBarService service, @Nullable Handler handler) {
+        mAmbientDisplayConfiguration = ambientDisplayConfiguration;
         try {
             service.setBiometicContextListener(new IBiometricContextListener.Stub() {
                 @Override
@@ -73,16 +98,70 @@
                     }
                 }
             });
+            service.registerSessionListener(SESSION_TYPES, new ISessionListener.Stub() {
+                @Override
+                public void onSessionStarted(int sessionType, InstanceId instance) {
+                    mSession.put(sessionType, instance);
+                }
+
+                @Override
+                public void onSessionEnded(int sessionType, InstanceId instance) {
+                    final InstanceId id = mSession.remove(sessionType);
+                    if (id != null && instance != null && id.getId() != instance.getId()) {
+                        Slog.w(TAG, "session id mismatch");
+                    }
+                }
+            });
         } catch (RemoteException e) {
             Slog.e(TAG, "Unable to register biometric context listener", e);
         }
     }
 
-    private boolean mIsDozing = false;
+    @Override
+    public OperationContext updateContext(@NonNull OperationContext operationContext,
+            boolean isCryptoOperation) {
+        operationContext.isAoD = isAoD();
+        operationContext.isCrypto = isCryptoOperation;
+        setFirstSessionId(operationContext);
+        return operationContext;
+    }
+
+    private void setFirstSessionId(@NonNull OperationContext operationContext) {
+        Integer sessionId = getKeyguardEntrySessionId();
+        if (sessionId != null) {
+            operationContext.id = sessionId;
+            operationContext.reason = OperationReason.KEYGUARD;
+            return;
+        }
+
+        sessionId = getBiometricPromptSessionId();
+        if (sessionId != null) {
+            operationContext.id = sessionId;
+            operationContext.reason = OperationReason.BIOMETRIC_PROMPT;
+            return;
+        }
+
+        operationContext.id = 0;
+        operationContext.reason = OperationReason.UNKNOWN;
+    }
+
+    @Nullable
+    @Override
+    public Integer getKeyguardEntrySessionId() {
+        final InstanceId id = mSession.get(StatusBarManager.SESSION_KEYGUARD);
+        return id != null ? id.getId() : null;
+    }
+
+    @Nullable
+    @Override
+    public Integer getBiometricPromptSessionId() {
+        final InstanceId id = mSession.get(StatusBarManager.SESSION_BIOMETRIC_PROMPT);
+        return id != null ? id.getId() : null;
+    }
 
     @Override
     public boolean isAoD() {
-        return mIsDozing;
+        return mIsDozing && mAmbientDisplayConfiguration.alwaysOnEnabled(UserHandle.USER_CURRENT);
     }
 
     @Override
@@ -98,7 +177,7 @@
 
     private void notifySubscribers() {
         mSubscribers.forEach((context, consumer) -> {
-            context.isAoD = mIsDozing;
+            context.isAoD = isAoD();
             consumer.accept(context);
         });
     }
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
index c3471bd..8965227 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
@@ -17,6 +17,8 @@
 package com.android.server.biometrics.log;
 
 import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.common.OperationReason;
 import android.util.Slog;
 
 import com.android.internal.util.FrameworkStatsLog;
@@ -33,42 +35,49 @@
 
     private BiometricFrameworkStatsLogger() {}
 
+    /** Shared instance. */
     public static BiometricFrameworkStatsLogger getInstance() {
         return sInstance;
     }
 
     /** {@see FrameworkStatsLog.BIOMETRIC_ACQUIRED}. */
-    public void acquired(
+    public void acquired(OperationContext operationContext,
             int statsModality, int statsAction, int statsClient, boolean isDebug,
-            int acquiredInfo, int vendorCode, boolean isCrypto, int targetUserId) {
+            int acquiredInfo, int vendorCode, int targetUserId) {
         FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ACQUIRED,
                 statsModality,
                 targetUserId,
-                isCrypto,
+                operationContext.isCrypto,
                 statsAction,
                 statsClient,
                 acquiredInfo,
                 vendorCode,
                 isDebug,
-                -1 /* sensorId */);
+                -1 /* sensorId */,
+                operationContext.id,
+                sessionType(operationContext.reason),
+                operationContext.isAoD);
     }
 
     /** {@see FrameworkStatsLog.BIOMETRIC_AUTHENTICATED}. */
-    public void authenticate(
+    public void authenticate(OperationContext operationContext,
             int statsModality, int statsAction, int statsClient, boolean isDebug, long latency,
-            boolean authenticated, int authState, boolean requireConfirmation, boolean isCrypto,
-            int targetUserId, boolean isBiometricPrompt, float ambientLightLux) {
+            int authState, boolean requireConfirmation,
+            int targetUserId, float ambientLightLux) {
         FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_AUTHENTICATED,
                 statsModality,
                 targetUserId,
-                isCrypto,
+                operationContext.isCrypto,
                 statsClient,
                 requireConfirmation,
                 authState,
                 sanitizeLatency(latency),
                 isDebug,
                 -1 /* sensorId */,
-                ambientLightLux);
+                ambientLightLux,
+                operationContext.id,
+                sessionType(operationContext.reason),
+                operationContext.isAoD);
     }
 
     /** {@see FrameworkStatsLog.BIOMETRIC_ENROLLED}. */
@@ -84,20 +93,23 @@
     }
 
     /** {@see FrameworkStatsLog.BIOMETRIC_ERROR_OCCURRED}. */
-    public void error(
+    public void error(OperationContext operationContext,
             int statsModality, int statsAction, int statsClient, boolean isDebug, long latency,
-            int error, int vendorCode, boolean isCrypto, int targetUserId) {
+            int error, int vendorCode, int targetUserId) {
         FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ERROR_OCCURRED,
                 statsModality,
                 targetUserId,
-                isCrypto,
+                operationContext.isCrypto,
                 statsAction,
                 statsClient,
                 error,
                 vendorCode,
                 isDebug,
                 sanitizeLatency(latency),
-                -1 /* sensorId */);
+                -1 /* sensorId */,
+                operationContext.id,
+                sessionType(operationContext.reason),
+                operationContext.isAoD);
     }
 
     /** {@see FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED}. */
@@ -123,4 +135,14 @@
         }
         return latency;
     }
+
+    private static int sessionType(@OperationReason byte reason) {
+        if (reason == OperationReason.BIOMETRIC_PROMPT) {
+            return BiometricsProtoEnums.SESSION_TYPE_BIOMETRIC_PROMPT;
+        }
+        if (reason == OperationReason.KEYGUARD) {
+            return BiometricsProtoEnums.SESSION_TYPE_KEYGUARD_ENTRY;
+        }
+        return BiometricsProtoEnums.SESSION_TYPE_UNKNOWN;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
index 0188390..2a8d9f1 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
@@ -25,6 +25,7 @@
 import android.hardware.SensorManager;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.common.OperationContext;
 import android.hardware.face.FaceManager;
 import android.hardware.fingerprint.FingerprintManager;
 import android.util.Slog;
@@ -144,8 +145,8 @@
     }
 
     /** Log an acquisition event. */
-    public void logOnAcquired(Context context,
-            int acquiredInfo, int vendorCode, boolean isCrypto, int targetUserId) {
+    public void logOnAcquired(Context context, OperationContext operationContext,
+            int acquiredInfo, int vendorCode, int targetUserId) {
         if (!mShouldLogMetrics) {
             return;
         }
@@ -165,7 +166,7 @@
         if (DEBUG) {
             Slog.v(TAG, "Acquired! Modality: " + mStatsModality
                     + ", User: " + targetUserId
-                    + ", IsCrypto: " + isCrypto
+                    + ", IsCrypto: " + operationContext.isCrypto
                     + ", Action: " + mStatsAction
                     + ", Client: " + mStatsClient
                     + ", AcquiredInfo: " + acquiredInfo
@@ -176,14 +177,14 @@
             return;
         }
 
-        mSink.acquired(mStatsModality, mStatsAction, mStatsClient,
+        mSink.acquired(operationContext, mStatsModality, mStatsAction, mStatsClient,
                 Utils.isDebugEnabled(context, targetUserId),
-                acquiredInfo, vendorCode, isCrypto, targetUserId);
+                acquiredInfo, vendorCode, targetUserId);
     }
 
     /** Log an error during an operation. */
-    public void logOnError(Context context,
-            int error, int vendorCode, boolean isCrypto, int targetUserId) {
+    public void logOnError(Context context, OperationContext operationContext,
+            int error, int vendorCode, int targetUserId) {
         if (!mShouldLogMetrics) {
             return;
         }
@@ -194,7 +195,7 @@
         if (DEBUG) {
             Slog.v(TAG, "Error! Modality: " + mStatsModality
                     + ", User: " + targetUserId
-                    + ", IsCrypto: " + isCrypto
+                    + ", IsCrypto: " + operationContext.isCrypto
                     + ", Action: " + mStatsAction
                     + ", Client: " + mStatsClient
                     + ", Error: " + error
@@ -208,14 +209,14 @@
             return;
         }
 
-        mSink.error(mStatsModality, mStatsAction, mStatsClient,
+        mSink.error(operationContext, mStatsModality, mStatsAction, mStatsClient,
                 Utils.isDebugEnabled(context, targetUserId), latency,
-                error, vendorCode, isCrypto, targetUserId);
+                error, vendorCode, targetUserId);
     }
 
     /** Log authentication attempt. */
-    public void logOnAuthenticated(Context context,
-            boolean authenticated, boolean requireConfirmation, boolean isCrypto,
+    public void logOnAuthenticated(Context context, OperationContext operationContext,
+            boolean authenticated, boolean requireConfirmation,
             int targetUserId, boolean isBiometricPrompt) {
         if (!mShouldLogMetrics) {
             return;
@@ -241,7 +242,7 @@
         if (DEBUG) {
             Slog.v(TAG, "Authenticated! Modality: " + mStatsModality
                     + ", User: " + targetUserId
-                    + ", IsCrypto: " + isCrypto
+                    + ", IsCrypto: " + operationContext.isCrypto
                     + ", Client: " + mStatsClient
                     + ", RequireConfirmation: " + requireConfirmation
                     + ", State: " + authState
@@ -255,10 +256,9 @@
             return;
         }
 
-        mSink.authenticate(mStatsModality, mStatsAction, mStatsClient,
+        mSink.authenticate(operationContext, mStatsModality, mStatsAction, mStatsClient,
                 Utils.isDebugEnabled(context, targetUserId),
-                latency, authenticated, authState, requireConfirmation, isCrypto,
-                targetUserId, isBiometricPrompt, mLastAmbientLux);
+                latency, authState, requireConfirmation, targetUserId, mLastAmbientLux);
     }
 
     /** Log enrollment outcome. */
diff --git a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
index e07a68c..0f0032b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
@@ -110,8 +110,8 @@
         // that do not handle lockout under the HAL. In these cases, ensure that the framework only
         // sends errors once per ClientMonitor.
         if (mShouldSendErrorToClient) {
-            getLogger().logOnError(getContext(), errorCode, vendorCode,
-                    isCryptoOperation(), getTargetUserId());
+            getLogger().logOnError(getContext(), getOperationContext(),
+                    errorCode, vendorCode, getTargetUserId());
             try {
                 if (getListener() != null) {
                     mShouldSendErrorToClient = false;
@@ -169,8 +169,8 @@
 
     protected final void onAcquiredInternal(int acquiredInfo, int vendorCode,
             boolean shouldSend) {
-        getLogger().logOnAcquired(getContext(), acquiredInfo, vendorCode,
-                isCryptoOperation(), getTargetUserId());
+        getLogger().logOnAcquired(getContext(), getOperationContext(),
+                acquiredInfo, vendorCode, getTargetUserId());
         if (DEBUG) {
             Slog.v(TAG, "Acquired: " + acquiredInfo + " " + vendorCode
                     + ", shouldSend: " + shouldSend);
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index 949edd0..54b79e1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -166,8 +166,8 @@
     @Override
     public void onAuthenticated(BiometricAuthenticator.Identifier identifier,
             boolean authenticated, ArrayList<Byte> hardwareAuthToken) {
-        getLogger().logOnAuthenticated(getContext(), authenticated, mRequireConfirmation,
-                isCryptoOperation(), getTargetUserId(), isBiometricPrompt());
+        getLogger().logOnAuthenticated(getContext(), getOperationContext(),
+                authenticated, mRequireConfirmation, getTargetUserId(), isBiometricPrompt());
 
         final ClientMonitorCallbackConverter listener = getListener();
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java
index eabafce..a6e8911 100644
--- a/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java
@@ -37,7 +37,7 @@
     protected final Supplier<T> mLazyDaemon;
 
     @NonNull
-    protected final OperationContext mOperationContext = new OperationContext();
+    private final OperationContext mOperationContext = new OperationContext();
 
     /**
      * @param context    system_server context
@@ -82,6 +82,23 @@
         super.destroy();
 
         // subclasses should do this earlier in most cases, but ensure it happens now
+        unsubscribeBiometricContext();
+    }
+
+    protected OperationContext getOperationContext() {
+        return getBiometricContext().updateContext(mOperationContext, isCryptoOperation());
+    }
+
+    protected ClientMonitorCallback getBiometricContextUnsubscriber() {
+        return new ClientMonitorCallback() {
+            @Override
+            public void onClientFinished(@NonNull BaseClientMonitor monitor, boolean success) {
+                unsubscribeBiometricContext();
+            }
+        };
+    }
+
+    protected void unsubscribeBiometricContext() {
         getBiometricContext().unsubscribe(mOperationContext);
     }
 }
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 039b08e..2e82057 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
@@ -57,6 +57,7 @@
 import com.android.server.ServiceThread;
 import com.android.server.SystemService;
 import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
@@ -645,7 +646,7 @@
                 try {
                     final SensorProps[] props = face.getSensorProps();
                     final FaceProvider provider = new FaceProvider(getContext(), props, instance,
-                            mLockoutResetDispatcher);
+                            mLockoutResetDispatcher, BiometricContext.getInstance(getContext()));
                     mServiceProviders.add(provider);
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index 75a1e0c..7765ab3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -26,8 +26,6 @@
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricFaceConstants;
 import android.hardware.biometrics.common.ICancellationSignal;
-import android.hardware.biometrics.common.OperationContext;
-import android.hardware.biometrics.common.OperationReason;
 import android.hardware.biometrics.face.IFace;
 import android.hardware.face.FaceAuthenticationFrame;
 import android.hardware.face.FaceManager;
@@ -157,13 +155,8 @@
         final AidlSession session = getFreshDaemon();
 
         if (session.hasContextMethods()) {
-            final OperationContext context = new OperationContext();
-            // TODO: add reason, id
-            context.id = 0;
-            context.reason = OperationReason.UNKNOWN;
-            context.isAoD = getBiometricContext().isAoD();
-            context.isCrypto = isCryptoOperation();
-            return session.getSession().authenticateWithContext(mOperationId, context);
+            return session.getSession().authenticateWithContext(
+                    mOperationId, getOperationContext());
         } else {
             return session.getSession().authenticate(mOperationId);
         }
@@ -282,8 +275,8 @@
         mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_TIMED);
         // Lockout metrics are logged as an error code.
         final int error = BiometricFaceConstants.FACE_ERROR_LOCKOUT;
-        getLogger().logOnError(getContext(), error, 0 /* vendorCode */,
-                isCryptoOperation(), getTargetUserId());
+        getLogger().logOnError(getContext(), getOperationContext(),
+                error, 0 /* vendorCode */, getTargetUserId());
 
         try {
             getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
@@ -298,8 +291,8 @@
         mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_PERMANENT);
         // Lockout metrics are logged as an error code.
         final int error = BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT;
-        getLogger().logOnError(getContext(), error, 0 /* vendorCode */,
-                isCryptoOperation(), getTargetUserId());
+        getLogger().logOnError(getContext(), getOperationContext(),
+                error, 0 /* vendorCode */, getTargetUserId());
 
         try {
             getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
index c79e601..efedcf8 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
@@ -22,8 +22,6 @@
 import android.hardware.SensorPrivacyManager;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.common.ICancellationSignal;
-import android.hardware.biometrics.common.OperationContext;
-import android.hardware.biometrics.common.OperationReason;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -116,13 +114,7 @@
         final AidlSession session = getFreshDaemon();
 
         if (session.hasContextMethods()) {
-            final OperationContext context = new OperationContext();
-            // TODO: add reason, id
-            context.id = 0;
-            context.reason = OperationReason.UNKNOWN;
-            context.isAoD = getBiometricContext().isAoD();
-            context.isCrypto = isCryptoOperation();
-            return session.getSession().detectInteractionWithContext(context);
+            return session.getSession().detectInteractionWithContext(getOperationContext());
         } else {
             return session.getSession().detectInteraction();
         }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
index 6f6dadc8..da78536 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
@@ -21,8 +21,6 @@
 import android.content.Context;
 import android.hardware.biometrics.BiometricFaceConstants;
 import android.hardware.biometrics.common.ICancellationSignal;
-import android.hardware.biometrics.common.OperationContext;
-import android.hardware.biometrics.common.OperationReason;
 import android.hardware.biometrics.face.EnrollmentType;
 import android.hardware.biometrics.face.Feature;
 import android.hardware.biometrics.face.IFace;
@@ -200,14 +198,8 @@
                 HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken);
 
         if (session.hasContextMethods()) {
-            final OperationContext context = new OperationContext();
-            // TODO: add reason, id
-            context.id = 0;
-            context.reason = OperationReason.UNKNOWN;
-            context.isAoD = getBiometricContext().isAoD();
-            context.isCrypto = isCryptoOperation();
             return session.getSession().enrollWithContext(
-                    hat, EnrollmentType.DEFAULT, features, mHwPreviewHandle, context);
+                    hat, EnrollmentType.DEFAULT, features, mHwPreviewHandle, getOperationContext());
         } else {
             return session.getSession().enroll(hat, EnrollmentType.DEFAULT, features,
                     mHwPreviewHandle);
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 64b0892..4e03ee9 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
@@ -90,7 +90,7 @@
     @NonNull private final BiometricTaskStackListener mTaskStackListener;
     // for requests that do not use biometric prompt
     @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
-
+    @NonNull private final BiometricContext mBiometricContext;
     @Nullable private IFace mDaemon;
 
     private final class BiometricTaskStackListener extends TaskStackListener {
@@ -128,7 +128,8 @@
 
     public FaceProvider(@NonNull Context context, @NonNull SensorProps[] props,
             @NonNull String halInstanceName,
-            @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull BiometricContext biometricContext) {
         mContext = context;
         mHalInstanceName = halInstanceName;
         mSensors = new SparseArray<>();
@@ -137,6 +138,7 @@
         mLockoutResetDispatcher = lockoutResetDispatcher;
         mActivityTaskManager = ActivityTaskManager.getInstance();
         mTaskStackListener = new BiometricTaskStackListener();
+        mBiometricContext = biometricContext;
 
         for (SensorProps prop : props) {
             final int sensorId = prop.commonProps.sensorId;
@@ -156,7 +158,7 @@
                     prop.supportsDetectInteraction, prop.halControlsPreview,
                     false /* resetLockoutRequiresChallenge */);
             final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler,
-                    internalProp, lockoutResetDispatcher);
+                    internalProp, lockoutResetDispatcher, mBiometricContext);
 
             mSensors.put(sensorId, sensor);
             Slog.d(getTag(), "Added: " + internalProp);
@@ -242,7 +244,7 @@
                     mContext.getOpPackageName(), sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
-                    BiometricContext.getInstance(),
+                    mBiometricContext,
                     mSensors.get(sensorId).getAuthenticatorIds());
 
             scheduleForSensor(sensorId, client);
@@ -253,7 +255,8 @@
         mHandler.post(() -> {
             final InvalidationRequesterClient<Face> client =
                     new InvalidationRequesterClient<>(mContext, userId, sensorId,
-                            BiometricLogger.ofUnknown(mContext), BiometricContext.getInstance(),
+                            BiometricLogger.ofUnknown(mContext),
+                            mBiometricContext,
                             FaceUtils.getInstance(sensorId));
             scheduleForSensor(sensorId, client);
         });
@@ -294,7 +297,7 @@
                     mSensors.get(sensorId).getLazySession(), userId, sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
-                    BiometricContext.getInstance(),
+                    mBiometricContext,
                     mSensors.get(sensorId).getAuthenticatorIds(), callback);
             scheduleForSensor(sensorId, client);
         });
@@ -324,7 +327,7 @@
                     new ClientMonitorCallbackConverter(receiver), userId, opPackageName, sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
-                    BiometricContext.getInstance());
+                    mBiometricContext);
             scheduleForSensor(sensorId, client);
         });
     }
@@ -337,7 +340,7 @@
                     mSensors.get(sensorId).getLazySession(), token, userId, opPackageName, sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
-                    BiometricContext.getInstance(), challenge);
+                    mBiometricContext, challenge);
             scheduleForSensor(sensorId, client);
         });
     }
@@ -358,7 +361,7 @@
                     ENROLL_TIMEOUT_SEC, previewSurface, sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_ENROLL,
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
-                    BiometricContext.getInstance(), maxTemplatesPerUser, debugConsent);
+                    mBiometricContext, maxTemplatesPerUser, debugConsent);
             scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
                 @Override
                 public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
@@ -391,7 +394,7 @@
                     mSensors.get(sensorId).getLazySession(),
                     token, id, callback, userId, opPackageName, sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
-                    BiometricContext.getInstance(), isStrongBiometric);
+                    mBiometricContext, isStrongBiometric);
             scheduleForSensor(sensorId, client);
         });
 
@@ -416,7 +419,7 @@
                     userId, operationId, restricted, opPackageName, cookie,
                     false /* requireConfirmation */, sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
-                    BiometricContext.getInstance(), isStrongBiometric,
+                    mBiometricContext, isStrongBiometric,
                     mUsageStats, mSensors.get(sensorId).getLockoutCache(),
                     allowBackgroundAuthentication, isKeyguardBypassEnabled);
             scheduleForSensor(sensorId, client);
@@ -472,7 +475,7 @@
                     opPackageName, FaceUtils.getInstance(sensorId), sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_REMOVE,
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
-                    BiometricContext.getInstance(),
+                    mBiometricContext,
                     mSensors.get(sensorId).getAuthenticatorIds());
             scheduleForSensor(sensorId, client);
         });
@@ -486,7 +489,7 @@
                     mContext.getOpPackageName(), sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
-                    BiometricContext.getInstance(), hardwareAuthToken,
+                    mBiometricContext, hardwareAuthToken,
                     mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher);
 
             scheduleForSensor(sensorId, client);
@@ -508,7 +511,7 @@
                     mSensors.get(sensorId).getLazySession(), token,
                     new ClientMonitorCallbackConverter(receiver), userId,
                     mContext.getOpPackageName(), sensorId,
-                    BiometricLogger.ofUnknown(mContext), BiometricContext.getInstance(),
+                    BiometricLogger.ofUnknown(mContext), mBiometricContext,
                     feature, enabled, hardwareAuthToken);
             scheduleForSensor(sensorId, client);
         });
@@ -527,7 +530,7 @@
             final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext,
                     mSensors.get(sensorId).getLazySession(), token, callback, userId,
                     mContext.getOpPackageName(), sensorId, BiometricLogger.ofUnknown(mContext),
-                    BiometricContext.getInstance());
+                    mBiometricContext);
             scheduleForSensor(sensorId, client);
         });
     }
@@ -550,7 +553,7 @@
                             mContext.getOpPackageName(), sensorId,
                             createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
                                     BiometricsProtoEnums.CLIENT_UNKNOWN),
-                            BiometricContext.getInstance(), enrolledList,
+                            mBiometricContext, enrolledList,
                             FaceUtils.getInstance(sensorId),
                             mSensors.get(sensorId).getAuthenticatorIds());
             scheduleForSensor(sensorId, client, callback);
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 fa07d12..b69c760 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
@@ -476,7 +476,8 @@
 
     Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
             @NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties,
-            @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull BiometricContext biometricContext) {
         mTag = tag;
         mProvider = provider;
         mContext = context;
@@ -492,7 +493,7 @@
                     public StopUserClient<?> getStopUserClient(int userId) {
                         return new FaceStopUserClient(mContext, mLazySession, mToken, userId,
                                 mSensorProperties.sensorId,
-                                BiometricLogger.ofUnknown(mContext), BiometricContext.getInstance(),
+                                BiometricLogger.ofUnknown(mContext), biometricContext,
                                 () -> mCurrentSession = null);
                     }
 
@@ -529,7 +530,7 @@
 
                         return new FaceStartUserClient(mContext, provider::getHalInstance,
                                 mToken, newUserId, mSensorProperties.sensorId,
-                                BiometricLogger.ofUnknown(mContext), BiometricContext.getInstance(),
+                                BiometricLogger.ofUnknown(mContext), biometricContext,
                                 resultController, userStartedCallback);
                     }
                 });
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 be1ed7d..73c759f 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
@@ -119,6 +119,7 @@
     @NonNull private final Map<Integer, Long> mAuthenticatorIds;
     @Nullable private IBiometricsFace mDaemon;
     @NonNull private final HalResultController mHalResultController;
+    @NonNull private final BiometricContext mBiometricContext;
     // for requests that do not use biometric prompt
     @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
     private int mCurrentUserId = UserHandle.USER_NULL;
@@ -155,6 +156,7 @@
         @NonNull private final LockoutHalImpl mLockoutTracker;
         @NonNull private final LockoutResetDispatcher mLockoutResetDispatcher;
 
+
         HalResultController(int sensorId, @NonNull Context context, @NonNull Handler handler,
                 @NonNull BiometricScheduler scheduler, @NonNull LockoutHalImpl lockoutTracker,
                 @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
@@ -337,12 +339,14 @@
             @NonNull FaceSensorPropertiesInternal sensorProps,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull Handler handler,
-            @NonNull BiometricScheduler scheduler) {
+            @NonNull BiometricScheduler scheduler,
+            @NonNull BiometricContext biometricContext) {
         mSensorProperties = sensorProps;
         mContext = context;
         mSensorId = sensorProps.sensorId;
         mScheduler = scheduler;
         mHandler = handler;
+        mBiometricContext = biometricContext;
         mUsageStats = new UsageStats(context);
         mAuthenticatorIds = new HashMap<>();
         mLazyDaemon = Face10.this::getDaemon;
@@ -367,7 +371,8 @@
         final Handler handler = new Handler(Looper.getMainLooper());
         return new Face10(context, sensorProps, lockoutResetDispatcher, handler,
                 new BiometricScheduler(TAG, BiometricScheduler.SENSOR_TYPE_FACE,
-                        null /* gestureAvailabilityTracker */));
+                        null /* gestureAvailabilityTracker */),
+                BiometricContext.getInstance(context));
     }
 
     @Override
@@ -538,7 +543,7 @@
                     opPackageName, mSensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
-                    BiometricContext.getInstance(), sSystemClock.millis());
+                    mBiometricContext, sSystemClock.millis());
             mGeneratedChallengeCache = client;
             mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
                 @Override
@@ -570,7 +575,7 @@
                     mLazyDaemon, token, userId, opPackageName, mSensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
-                    BiometricContext.getInstance());
+                    mBiometricContext);
             mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
                 @Override
                 public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
@@ -601,7 +606,7 @@
                     ENROLL_TIMEOUT_SEC, previewSurface, mSensorId,
                     createLogger(BiometricsProtoEnums.ACTION_ENROLL,
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
-                    BiometricContext.getInstance());
+                    mBiometricContext);
 
             mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
                 @Override
@@ -649,8 +654,8 @@
                     mLazyDaemon, token, requestId, receiver, userId, operationId, restricted,
                     opPackageName, cookie, false /* requireConfirmation */, mSensorId,
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
-                    BiometricContext.getInstance(), isStrongBiometric, mLockoutTracker, mUsageStats,
-                    allowBackgroundAuthentication, isKeyguardBypassEnabled);
+                    mBiometricContext, isStrongBiometric, mLockoutTracker,
+                    mUsageStats, allowBackgroundAuthentication, isKeyguardBypassEnabled);
             mScheduler.scheduleClientMonitor(client);
         });
     }
@@ -685,7 +690,7 @@
                     FaceUtils.getLegacyInstance(mSensorId), mSensorId,
                     createLogger(BiometricsProtoEnums.ACTION_REMOVE,
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
-                    BiometricContext.getInstance(), mAuthenticatorIds);
+                    mBiometricContext, mAuthenticatorIds);
             mScheduler.scheduleClientMonitor(client);
         });
     }
@@ -703,7 +708,7 @@
                     FaceUtils.getLegacyInstance(mSensorId), mSensorId,
                     createLogger(BiometricsProtoEnums.ACTION_REMOVE,
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
-                    BiometricContext.getInstance(), mAuthenticatorIds);
+                    mBiometricContext, mAuthenticatorIds);
             mScheduler.scheduleClientMonitor(client);
         });
     }
@@ -722,7 +727,7 @@
                     mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
-                    BiometricContext.getInstance(), hardwareAuthToken);
+                    mBiometricContext, hardwareAuthToken);
             mScheduler.scheduleClientMonitor(client);
         });
     }
@@ -744,7 +749,8 @@
             final FaceSetFeatureClient client = new FaceSetFeatureClient(mContext,
                     mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
                     opPackageName, mSensorId, BiometricLogger.ofUnknown(mContext),
-                    BiometricContext.getInstance(), feature, enabled, hardwareAuthToken, faceId);
+                    mBiometricContext,
+                    feature, enabled, hardwareAuthToken, faceId);
             mScheduler.scheduleClientMonitor(client);
         });
     }
@@ -764,7 +770,7 @@
             final int faceId = faces.get(0).getBiometricId();
             final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext, mLazyDaemon,
                     token, listener, userId, opPackageName, mSensorId,
-                    BiometricLogger.ofUnknown(mContext), BiometricContext.getInstance(),
+                    BiometricLogger.ofUnknown(mContext), mBiometricContext,
                     feature, faceId);
             mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
                 @Override
@@ -793,7 +799,7 @@
                     mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
                     createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
-                    BiometricContext.getInstance(), enrolledList,
+                    mBiometricContext, enrolledList,
                     FaceUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
             mScheduler.scheduleClientMonitor(client, callback);
         });
@@ -918,7 +924,7 @@
                 mLazyDaemon, targetUserId, mContext.getOpPackageName(), mSensorId,
                 createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
                         BiometricsProtoEnums.CLIENT_UNKNOWN),
-                BiometricContext.getInstance(), hasEnrolled, mAuthenticatorIds);
+                mBiometricContext, hasEnrolled, mAuthenticatorIds);
         mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
             @Override
             public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
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 6366e19..b4befd2 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
@@ -83,6 +83,7 @@
 import com.android.server.ServiceThread;
 import com.android.server.SystemService;
 import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
@@ -815,7 +816,8 @@
                         UserHandle.USER_CURRENT) != 0) {
                     fingerprint21 = Fingerprint21UdfpsMock.newInstance(getContext(),
                             mFingerprintStateCallback, hidlSensor,
-                            mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
+                            mLockoutResetDispatcher, mGestureAvailabilityDispatcher,
+                            BiometricContext.getInstance(getContext()));
                 } else {
                     fingerprint21 = Fingerprint21.newInstance(getContext(),
                             mFingerprintStateCallback, hidlSensor, mHandler,
@@ -843,7 +845,8 @@
                     final FingerprintProvider provider =
                             new FingerprintProvider(getContext(), mFingerprintStateCallback, props,
                                     instance, mLockoutResetDispatcher,
-                                    mGestureAvailabilityDispatcher);
+                                    mGestureAvailabilityDispatcher,
+                                    BiometricContext.getInstance(getContext()));
                     mServiceProviders.add(provider);
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 184e1ea..d26a780 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -24,7 +24,7 @@
 import android.hardware.biometrics.BiometricFingerprintConstants;
 import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired;
 import android.hardware.biometrics.common.ICancellationSignal;
-import android.hardware.biometrics.common.OperationReason;
+import android.hardware.biometrics.common.OperationContext;
 import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.ISidefpsController;
@@ -108,7 +108,8 @@
     @NonNull
     @Override
     protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
-        return new ClientMonitorCompositeCallback(mALSProbeCallback, callback);
+        return new ClientMonitorCompositeCallback(mALSProbeCallback,
+                getBiometricContextUnsubscriber(), callback);
     }
 
     @Override
@@ -178,12 +179,17 @@
         final AidlSession session = getFreshDaemon();
 
         if (session.hasContextMethods()) {
-            // TODO: add reason, id
-            mOperationContext.id = 0;
-            mOperationContext.reason = OperationReason.UNKNOWN;
-            mOperationContext.isAoD = getBiometricContext().isAoD();
-            mOperationContext.isCrypto = isCryptoOperation();
-            return session.getSession().authenticateWithContext(mOperationId, mOperationContext);
+            final OperationContext opContext = getOperationContext();
+            final ICancellationSignal cancel =  session.getSession().authenticateWithContext(
+                    mOperationId, opContext);
+            getBiometricContext().subscribe(opContext, ctx -> {
+                try {
+                    session.getSession().onContextChanged(ctx);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Unable to notify context changed", e);
+                }
+            });
+            return cancel;
         } else {
             return session.getSession().authenticate(mOperationId);
         }
@@ -192,6 +198,8 @@
     @Override
     protected void stopHalOperation() {
         mSensorOverlays.hide(getSensorId());
+        unsubscribeBiometricContext();
+
         if (mCancellationSignal != null) {
             try {
                 mCancellationSignal.cancel();
@@ -221,7 +229,7 @@
                 context.y = y;
                 context.minor = minor;
                 context.major = major;
-                context.isAoD = false; // TODO; get value
+                context.isAoD = getBiometricContext().isAoD();
                 session.getSession().onPointerDownWithContext(context);
             } else {
                 session.getSession().onPointerDown(0 /* pointerId */, x, y, minor, major);
@@ -279,8 +287,8 @@
         mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_TIMED);
         // Lockout metrics are logged as an error code.
         final int error = BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
-        getLogger().logOnError(getContext(), error, 0 /* vendorCode */,
-                isCryptoOperation(), getTargetUserId());
+        getLogger().logOnError(getContext(), getOperationContext(),
+                error, 0 /* vendorCode */, getTargetUserId());
 
         try {
             getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
@@ -298,8 +306,8 @@
         mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_PERMANENT);
         // Lockout metrics are logged as an error code.
         final int error = BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
-        getLogger().logOnError(getContext(), error, 0 /* vendorCode */,
-                isCryptoOperation(), getTargetUserId());
+        getLogger().logOnError(getContext(), getOperationContext(),
+                error, 0 /* vendorCode */, getTargetUserId());
 
         try {
             getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index 9d348e1..0e89814 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -21,8 +21,6 @@
 import android.content.Context;
 import android.hardware.biometrics.BiometricOverlayConstants;
 import android.hardware.biometrics.common.ICancellationSignal;
-import android.hardware.biometrics.common.OperationContext;
-import android.hardware.biometrics.common.OperationReason;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -99,13 +97,7 @@
         final AidlSession session = getFreshDaemon();
 
         if (session.hasContextMethods()) {
-            final OperationContext context = new OperationContext();
-            // TODO: add reason, id
-            context.id = 0;
-            context.reason = OperationReason.UNKNOWN;
-            context.isAoD = getBiometricContext().isAoD();
-            context.isCrypto = isCryptoOperation();
-            return session.getSession().detectInteractionWithContext(context);
+            return session.getSession().detectInteractionWithContext(getOperationContext());
         } else {
             return session.getSession().detectInteraction();
         }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index ed16a6d..e21d901 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -24,7 +24,6 @@
 import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired;
 import android.hardware.biometrics.common.ICancellationSignal;
 import android.hardware.biometrics.common.OperationContext;
-import android.hardware.biometrics.common.OperationReason;
 import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintManager;
@@ -39,6 +38,8 @@
 import com.android.server.biometrics.HardwareAuthTokenUtils;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.log.CallbackWithProbe;
+import com.android.server.biometrics.log.Probe;
 import com.android.server.biometrics.sensors.BiometricNotificationUtils;
 import com.android.server.biometrics.sensors.BiometricUtils;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -58,6 +59,7 @@
 
     @NonNull private final FingerprintSensorPropertiesInternal mSensorProps;
     @NonNull private final SensorOverlays mSensorOverlays;
+    @NonNull private final CallbackWithProbe<Probe> mALSProbeCallback;
 
     private final @FingerprintManager.EnrollReason int mEnrollReason;
     @Nullable private ICancellationSignal mCancellationSignal;
@@ -83,6 +85,8 @@
         mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
         mMaxTemplatesPerUser = maxTemplatesPerUser;
 
+        mALSProbeCallback = getLogger().createALSCallback(false /* startWithClient */);
+
         mEnrollReason = enrollReason;
         if (enrollReason == FingerprintManager.ENROLL_FIND_SENSOR) {
             getLogger().disableMetrics();
@@ -92,8 +96,8 @@
     @NonNull
     @Override
     protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
-        return new ClientMonitorCompositeCallback(
-                getLogger().createALSCallback(true /* startWithClient */), callback);
+        return new ClientMonitorCompositeCallback(mALSProbeCallback,
+                getBiometricContextUnsubscriber(), callback);
     }
 
     @Override
@@ -142,22 +146,6 @@
     }
 
     @Override
-    protected void stopHalOperation() {
-        mSensorOverlays.hide(getSensorId());
-
-        if (mCancellationSignal != null) {
-            try {
-                mCancellationSignal.cancel();
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Remote exception when requesting cancel", e);
-                onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
-                        0 /* vendorCode */);
-                mCallback.onClientFinished(this, false /* success */);
-            }
-        }
-    }
-
-    @Override
     protected void startHalOperation() {
         mSensorOverlays.show(getSensorId(), getOverlayReasonFromEnrollReason(mEnrollReason), this);
 
@@ -178,22 +166,44 @@
                 HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken);
 
         if (session.hasContextMethods()) {
-            final OperationContext context = new OperationContext();
-            // TODO: add reason, id
-            context.id = 0;
-            context.reason = OperationReason.UNKNOWN;
-            context.isAoD = getBiometricContext().isAoD();
-            context.isCrypto = isCryptoOperation();
-            return session.getSession().enrollWithContext(hat, context);
+            final OperationContext opContext = getOperationContext();
+            final ICancellationSignal cancel = session.getSession().enrollWithContext(
+                    hat, opContext);
+            getBiometricContext().subscribe(opContext, ctx -> {
+                try {
+                    session.getSession().onContextChanged(ctx);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Unable to notify context changed", e);
+                }
+            });
+            return cancel;
         } else {
             return session.getSession().enroll(hat);
         }
     }
 
     @Override
+    protected void stopHalOperation() {
+        mSensorOverlays.hide(getSensorId());
+        unsubscribeBiometricContext();
+
+        if (mCancellationSignal != null) {
+            try {
+                mCancellationSignal.cancel();
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote exception when requesting cancel", e);
+                onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
+                        0 /* vendorCode */);
+                mCallback.onClientFinished(this, false /* success */);
+            }
+        }
+    }
+
+    @Override
     public void onPointerDown(int x, int y, float minor, float major) {
         try {
             mIsPointerDown = true;
+            mALSProbeCallback.getProbe().enable();
 
             final AidlSession session = getFreshDaemon();
             if (session.hasContextMethods()) {
@@ -203,7 +213,7 @@
                 context.y = y;
                 context.minor = minor;
                 context.major = major;
-                context.isAoD = false;
+                context.isAoD = getBiometricContext().isAoD();
                 session.getSession().onPointerDownWithContext(context);
             } else {
                 session.getSession().onPointerDown(0 /* pointerId */, x, y, minor, major);
@@ -217,6 +227,7 @@
     public void onPointerUp() {
         try {
             mIsPointerDown = false;
+            mALSProbeCallback.getProbe().disable();
 
             final AidlSession session = getFreshDaemon();
             if (session.hasContextMethods()) {
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 221ff941..f810bca 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
@@ -101,7 +101,7 @@
     @NonNull private final BiometricTaskStackListener mTaskStackListener;
     // for requests that do not use biometric prompt
     @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
-
+    @NonNull private final BiometricContext mBiometricContext;
     @Nullable private IFingerprint mDaemon;
     @Nullable private IUdfpsOverlayController mUdfpsOverlayController;
     @Nullable private ISidefpsController mSidefpsController;
@@ -144,7 +144,8 @@
             @NonNull FingerprintStateCallback fingerprintStateCallback,
             @NonNull SensorProps[] props, @NonNull String halInstanceName,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
+            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+            @NonNull BiometricContext biometricContext) {
         mContext = context;
         mFingerprintStateCallback = fingerprintStateCallback;
         mHalInstanceName = halInstanceName;
@@ -153,6 +154,7 @@
         mLockoutResetDispatcher = lockoutResetDispatcher;
         mActivityTaskManager = ActivityTaskManager.getInstance();
         mTaskStackListener = new BiometricTaskStackListener();
+        mBiometricContext = biometricContext;
 
         final List<SensorLocationInternal> workaroundLocations = getWorkaroundSensorProps(context);
 
@@ -184,7 +186,8 @@
                                                     location.sensorRadius))
                                             .collect(Collectors.toList()));
             final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler,
-                    internalProp, lockoutResetDispatcher, gestureAvailabilityDispatcher);
+                    internalProp, lockoutResetDispatcher, gestureAvailabilityDispatcher,
+                    mBiometricContext);
 
             mSensors.put(sensorId, sensor);
             Slog.d(getTag(), "Added: " + internalProp);
@@ -303,7 +306,7 @@
                             mContext.getOpPackageName(), sensorId,
                             createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
                                     BiometricsProtoEnums.CLIENT_UNKNOWN),
-                            BiometricContext.getInstance(),
+                            mBiometricContext,
                             mSensors.get(sensorId).getAuthenticatorIds());
             scheduleForSensor(sensorId, client);
         });
@@ -313,7 +316,8 @@
         mHandler.post(() -> {
             final InvalidationRequesterClient<Fingerprint> client =
                     new InvalidationRequesterClient<>(mContext, userId, sensorId,
-                            BiometricLogger.ofUnknown(mContext), BiometricContext.getInstance(),
+                            BiometricLogger.ofUnknown(mContext),
+                            mBiometricContext,
                             FingerprintUtils.getInstance(sensorId));
             scheduleForSensor(sensorId, client);
         });
@@ -327,7 +331,7 @@
                     mContext.getOpPackageName(), sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
-                    BiometricContext.getInstance(), hardwareAuthToken,
+                    mBiometricContext, hardwareAuthToken,
                     mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher);
             scheduleForSensor(sensorId, client);
         });
@@ -343,7 +347,7 @@
                             new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
                             sensorId, createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
                                 BiometricsProtoEnums.CLIENT_UNKNOWN),
-                            BiometricContext.getInstance());
+                            mBiometricContext);
             scheduleForSensor(sensorId, client);
         });
     }
@@ -358,7 +362,7 @@
                             userId, opPackageName, sensorId,
                             createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
                                     BiometricsProtoEnums.CLIENT_UNKNOWN),
-                            BiometricContext.getInstance(), challenge);
+                            mBiometricContext, challenge);
             scheduleForSensor(sensorId, client);
         });
     }
@@ -378,7 +382,7 @@
                     opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_ENROLL,
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
-                    BiometricContext.getInstance(),
+                    mBiometricContext,
                     mSensors.get(sensorId).getSensorProperties(),
                     mUdfpsOverlayController, mSidefpsController, maxTemplatesPerUser, enrollReason);
             scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
@@ -419,7 +423,7 @@
                     mSensors.get(sensorId).getLazySession(), token, id, callback, userId,
                     opPackageName, sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
-                    BiometricContext.getInstance(),
+                    mBiometricContext,
                     mUdfpsOverlayController, isStrongBiometric);
             scheduleForSensor(sensorId, client, mFingerprintStateCallback);
         });
@@ -439,7 +443,7 @@
                     userId, operationId, restricted, opPackageName, cookie,
                     false /* requireConfirmation */, sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
-                    BiometricContext.getInstance(), isStrongBiometric,
+                    mBiometricContext, isStrongBiometric,
                     mTaskStackListener, mSensors.get(sensorId).getLockoutCache(),
                     mUdfpsOverlayController, mSidefpsController, allowBackgroundAuthentication,
                     mSensors.get(sensorId).getSensorProperties());
@@ -503,7 +507,7 @@
                     opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_REMOVE,
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
-                    BiometricContext.getInstance(),
+                    mBiometricContext,
                     mSensors.get(sensorId).getAuthenticatorIds());
             scheduleForSensor(sensorId, client, mFingerprintStateCallback);
         });
@@ -520,7 +524,7 @@
                             mContext.getOpPackageName(), sensorId,
                             createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
                                     BiometricsProtoEnums.CLIENT_UNKNOWN),
-                            BiometricContext.getInstance(),
+                            mBiometricContext,
                             enrolledList, FingerprintUtils.getInstance(sensorId),
                             mSensors.get(sensorId).getAuthenticatorIds());
             scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(callback,
@@ -559,7 +563,7 @@
                             mSensors.get(sensorId).getLazySession(), userId, sensorId,
                             createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
                                     BiometricsProtoEnums.CLIENT_UNKNOWN),
-                            BiometricContext.getInstance(),
+                            mBiometricContext,
                             mSensors.get(sensorId).getAuthenticatorIds(), callback);
             scheduleForSensor(sensorId, client);
         });
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 27226b3..63e345e 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
@@ -429,7 +429,8 @@
     Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
             @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
+            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+            @NonNull BiometricContext biometricContext) {
         mTag = tag;
         mProvider = provider;
         mContext = context;
@@ -447,7 +448,7 @@
                     public StopUserClient<?> getStopUserClient(int userId) {
                         return new FingerprintStopUserClient(mContext, mLazySession, mToken,
                                 userId, mSensorProperties.sensorId,
-                                BiometricLogger.ofUnknown(mContext), BiometricContext.getInstance(),
+                                BiometricLogger.ofUnknown(mContext), biometricContext,
                                 () -> mCurrentSession = null);
                     }
 
@@ -484,7 +485,7 @@
 
                         return new FingerprintStartUserClient(mContext, provider::getHalInstance,
                                 mToken, newUserId, mSensorProperties.sensorId,
-                                BiometricLogger.ofUnknown(mContext), BiometricContext.getInstance(),
+                                BiometricLogger.ofUnknown(mContext), biometricContext,
                                 resultController, userStartedCallback);
                     }
                 });
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 6c35c8c..9d60859 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
@@ -120,6 +120,7 @@
     @NonNull private final HalResultController mHalResultController;
     @Nullable private IUdfpsOverlayController mUdfpsOverlayController;
     @Nullable private ISidefpsController mSidefpsController;
+    @NonNull private final BiometricContext mBiometricContext;
     // for requests that do not use biometric prompt
     @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
     private int mCurrentUserId = UserHandle.USER_NULL;
@@ -320,15 +321,18 @@
         }
     }
 
+    @VisibleForTesting
     Fingerprint21(@NonNull Context context,
             @NonNull FingerprintStateCallback fingerprintStateCallback,
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
             @NonNull BiometricScheduler scheduler,
             @NonNull Handler handler,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull HalResultController controller) {
+            @NonNull HalResultController controller,
+            @NonNull BiometricContext biometricContext) {
         mContext = context;
         mFingerprintStateCallback = fingerprintStateCallback;
+        mBiometricContext = biometricContext;
 
         mSensorProperties = sensorProps;
         mSensorId = sensorProps.sensorId;
@@ -370,7 +374,7 @@
         final HalResultController controller = new HalResultController(sensorProps.sensorId,
                 context, handler, scheduler);
         return new Fingerprint21(context, fingerprintStateCallback, sensorProps, scheduler, handler,
-                lockoutResetDispatcher, controller);
+                lockoutResetDispatcher, controller, BiometricContext.getInstance(context));
     }
 
     @Override
@@ -497,7 +501,7 @@
                         mContext.getOpPackageName(), mSensorProperties.sensorId,
                         createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
                                 BiometricsProtoEnums.CLIENT_UNKNOWN),
-                        BiometricContext.getInstance(),
+                        mBiometricContext,
                         this::getCurrentUser, hasEnrolled, mAuthenticatorIds, force);
         mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
             @Override
@@ -544,7 +548,7 @@
                     userId, mContext.getOpPackageName(), sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
-                    BiometricContext.getInstance(), mLockoutTracker);
+                    mBiometricContext, mLockoutTracker);
             mScheduler.scheduleClientMonitor(client);
         });
     }
@@ -559,7 +563,7 @@
                             mSensorProperties.sensorId,
                             createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
                                     BiometricsProtoEnums.CLIENT_UNKNOWN),
-                            BiometricContext.getInstance());
+                            mBiometricContext);
             mScheduler.scheduleClientMonitor(client);
         });
     }
@@ -573,7 +577,7 @@
                     mSensorProperties.sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
-                    BiometricContext.getInstance());
+                    mBiometricContext);
             mScheduler.scheduleClientMonitor(client);
         });
     }
@@ -594,7 +598,7 @@
                     mSensorProperties.sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_ENROLL,
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
-                    BiometricContext.getInstance(),
+                    mBiometricContext,
                     mUdfpsOverlayController, mSidefpsController,
                     enrollReason);
             mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@@ -636,7 +640,8 @@
                     mLazyDaemon, token, id, listener, userId, opPackageName,
                     mSensorProperties.sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
-                    BiometricContext.getInstance(), mUdfpsOverlayController, isStrongBiometric);
+                    mBiometricContext, mUdfpsOverlayController,
+                    isStrongBiometric);
             mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback);
         });
 
@@ -657,7 +662,7 @@
                     restricted, opPackageName, cookie, false /* requireConfirmation */,
                     mSensorProperties.sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
-                    BiometricContext.getInstance(), isStrongBiometric,
+                    mBiometricContext, isStrongBiometric,
                     mTaskStackListener, mLockoutTracker,
                     mUdfpsOverlayController, mSidefpsController,
                     allowBackgroundAuthentication, mSensorProperties);
@@ -702,7 +707,7 @@
                     mSensorProperties.sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_REMOVE,
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
-                    BiometricContext.getInstance(), mAuthenticatorIds);
+                    mBiometricContext, mAuthenticatorIds);
             mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback);
         });
     }
@@ -722,7 +727,7 @@
                     mSensorProperties.sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_REMOVE,
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
-                    BiometricContext.getInstance(), mAuthenticatorIds);
+                    mBiometricContext, mAuthenticatorIds);
             mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback);
         });
     }
@@ -739,7 +744,7 @@
                     mSensorProperties.sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
-                    BiometricContext.getInstance(), enrolledList,
+                    mBiometricContext, enrolledList,
                     FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
             mScheduler.scheduleClientMonitor(client, callback);
         });
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
index 1694bd9..149526f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
@@ -37,6 +37,7 @@
 import android.util.SparseBooleanArray;
 
 import com.android.internal.R;
+import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.sensors.AuthenticationConsumer;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricScheduler;
@@ -247,7 +248,8 @@
             @NonNull FingerprintStateCallback fingerprintStateCallback,
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
+            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+            @NonNull BiometricContext biometricContext) {
         Slog.d(TAG, "Creating Fingerprint23Mock!");
 
         final Handler handler = new Handler(Looper.getMainLooper());
@@ -256,7 +258,7 @@
         final MockHalResultController controller =
                 new MockHalResultController(sensorProps.sensorId, context, handler, scheduler);
         return new Fingerprint21UdfpsMock(context, fingerprintStateCallback, sensorProps, scheduler,
-                handler, lockoutResetDispatcher, controller);
+                handler, lockoutResetDispatcher, controller, biometricContext);
     }
 
     private static abstract class FakeFingerRunnable implements Runnable {
@@ -385,9 +387,10 @@
             @NonNull TestableBiometricScheduler scheduler,
             @NonNull Handler handler,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull MockHalResultController controller) {
+            @NonNull MockHalResultController controller,
+            @NonNull BiometricContext biometricContext) {
         super(context, fingerprintStateCallback, sensorProps, scheduler, handler,
-                lockoutResetDispatcher, controller);
+                lockoutResetDispatcher, controller, biometricContext);
         mScheduler = scheduler;
         mScheduler.init(this);
         mHandler = handler;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
index f10d4e4..c2929d0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
@@ -130,8 +130,9 @@
     @Override
     public void onAuthenticated(BiometricAuthenticator.Identifier identifier, boolean authenticated,
             ArrayList<Byte> hardwareAuthToken) {
-        getLogger().logOnAuthenticated(getContext(), authenticated, false /* requireConfirmation */,
-                isCryptoOperation(), getTargetUserId(), false /* isBiometricPrompt */);
+        getLogger().logOnAuthenticated(getContext(), getOperationContext(),
+                authenticated, false /* requireConfirmation */,
+                getTargetUserId(), false /* isBiometricPrompt */);
 
         // Do not distinguish between success/failures.
         vibrateSuccess();
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 1e00ea9..1b0341c 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -308,7 +308,8 @@
         public void onFixedRotationFinished(int displayId) { }
 
         @Override
-        public void onKeepClearAreasChanged(int displayId, List<Rect> keepClearArea) { }
+        public void onKeepClearAreasChanged(int displayId, List<Rect> restricted,
+                List<Rect> unrestricted) { }
     }
 
 
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 1f44854..3494342 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1014,6 +1014,9 @@
                 mAutomaticBrightnessController.switchToInteractiveScreenBrightnessMode();
             }
         }
+        if (mDisplayWhiteBalanceController != null) {
+            mDisplayWhiteBalanceController.setStrongModeEnabled(isIdle);
+        }
     }
 
     private final Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() {
diff --git a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
index 151ec81..fb36dc7 100644
--- a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
+++ b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
@@ -44,26 +44,18 @@
         AmbientSensor.AmbientBrightnessSensor.Callbacks,
         AmbientSensor.AmbientColorTemperatureSensor.Callbacks {
 
-    protected static final String TAG = "DisplayWhiteBalanceController";
-    protected boolean mLoggingEnabled;
+    private static final String TAG = "DisplayWhiteBalanceController";
+    private boolean mLoggingEnabled;
 
-    private boolean mEnabled;
+    private final ColorDisplayServiceInternal mColorDisplayServiceInternal;
 
-    // To decouple the DisplayPowerController from the DisplayWhiteBalanceController, the DPC
-    // implements Callbacks and passes itself to the DWBC so it can call back into it without
-    // knowing about it.
-    private Callbacks mCallbacks;
-
-    private AmbientSensor.AmbientBrightnessSensor mBrightnessSensor;
-
+    private final AmbientSensor.AmbientBrightnessSensor mBrightnessSensor;
     @VisibleForTesting
     AmbientFilter mBrightnessFilter;
-    private AmbientSensor.AmbientColorTemperatureSensor mColorTemperatureSensor;
-
+    private final AmbientSensor.AmbientColorTemperatureSensor mColorTemperatureSensor;
     @VisibleForTesting
     AmbientFilter mColorTemperatureFilter;
-    private DisplayWhiteBalanceThrottler mThrottler;
-
+    private final DisplayWhiteBalanceThrottler mThrottler;
     // In low brightness conditions the ALS readings are more noisy and produce
     // high errors. This default is introduced to provide a fixed display color
     // temperature when sensor readings become unreliable.
@@ -74,16 +66,12 @@
     private final float mHighLightAmbientColorTemperature;
 
     private float mAmbientColorTemperature;
-
     @VisibleForTesting
     float mPendingAmbientColorTemperature;
     private float mLastAmbientColorTemperature;
 
-    private ColorDisplayServiceInternal mColorDisplayServiceInternal;
-
     // The most recent ambient color temperature values are kept for debugging purposes.
-    private static final int HISTORY_SIZE = 50;
-    private History mAmbientColorTemperatureHistory;
+    private final History mAmbientColorTemperatureHistory;
 
     // Override the ambient color temperature for debugging purposes.
     private float mAmbientColorTemperatureOverride;
@@ -91,6 +79,10 @@
     // A piecewise linear relationship between ambient and display color temperatures.
     private Spline.LinearSpline mAmbientToDisplayColorTemperatureSpline;
 
+    // A piecewise linear relationship between ambient and display color temperatures, with a
+    // stronger change between the two sets of values.
+    private Spline.LinearSpline mStrongAmbientToDisplayColorTemperatureSpline;
+
     // In very low or very high brightness conditions Display White Balance should
     // be to set to a default instead of using mAmbientToDisplayColorTemperatureSpline.
     // However, setting Display White Balance based on thresholds can cause the
@@ -109,6 +101,17 @@
     private float mLatestLowLightBias;
     private float mLatestHighLightBias;
 
+    private boolean mEnabled;
+
+    // Whether a higher-strength adjustment should be applied; this must be enabled in addition to
+    // mEnabled in order to be applied.
+    private boolean mStrongModeEnabled;
+
+    // To decouple the DisplayPowerController from the DisplayWhiteBalanceController, the DPC
+    // implements Callbacks and passes itself to the DWBC so it can call back into it without
+    // knowing about it.
+    private Callbacks mDisplayPowerControllerCallbacks;
+
     /**
      * @param brightnessSensor
      *      The sensor used to detect changes in the ambient brightness.
@@ -159,16 +162,18 @@
             @NonNull AmbientSensor.AmbientColorTemperatureSensor colorTemperatureSensor,
             @NonNull AmbientFilter colorTemperatureFilter,
             @NonNull DisplayWhiteBalanceThrottler throttler,
-            float[] lowLightAmbientBrightnesses, float[] lowLightAmbientBiases,
+            float[] lowLightAmbientBrightnesses,
+            float[] lowLightAmbientBiases,
             float lowLightAmbientColorTemperature,
-            float[] highLightAmbientBrightnesses, float[] highLightAmbientBiases,
+            float[] highLightAmbientBrightnesses,
+            float[] highLightAmbientBiases,
             float highLightAmbientColorTemperature,
-            float[] ambientColorTemperatures, float[] displayColorTemperatures) {
+            float[] ambientColorTemperatures,
+            float[] displayColorTemperatures,
+            float[] strongAmbientColorTemperatures,
+            float[] strongDisplayColorTemperatures) {
         validateArguments(brightnessSensor, brightnessFilter, colorTemperatureSensor,
                 colorTemperatureFilter, throttler);
-        mLoggingEnabled = false;
-        mEnabled = false;
-        mCallbacks = null;
         mBrightnessSensor = brightnessSensor;
         mBrightnessFilter = brightnessFilter;
         mColorTemperatureSensor = colorTemperatureSensor;
@@ -179,7 +184,7 @@
         mAmbientColorTemperature = -1.0f;
         mPendingAmbientColorTemperature = -1.0f;
         mLastAmbientColorTemperature = -1.0f;
-        mAmbientColorTemperatureHistory = new History(HISTORY_SIZE);
+        mAmbientColorTemperatureHistory = new History(/* size= */ 50);
         mAmbientColorTemperatureOverride = -1.0f;
 
         try {
@@ -235,6 +240,13 @@
             mAmbientToDisplayColorTemperatureSpline = null;
         }
 
+        try {
+            mStrongAmbientToDisplayColorTemperatureSpline = new Spline.LinearSpline(
+                    strongAmbientColorTemperatures, strongDisplayColorTemperatures);
+        } catch (Exception e) {
+            Slog.e(TAG, "Failed to create strong ambient to display color temperature spline", e);
+        }
+
         mColorDisplayServiceInternal = LocalServices.getService(ColorDisplayServiceInternal.class);
     }
 
@@ -255,6 +267,19 @@
     }
 
     /**
+     * Enable/disable the stronger adjustment option.
+     *
+     * @param enabled whether the stronger adjustment option should be turned on
+     */
+    public void setStrongModeEnabled(boolean enabled) {
+        mStrongModeEnabled = enabled;
+        if (mEnabled) {
+            updateAmbientColorTemperature();
+            updateDisplayColorTemperature();
+        }
+    }
+
+    /**
      * Set an object to call back to when the display color temperature should be updated.
      *
      * @param callbacks
@@ -263,10 +288,10 @@
      * @return Whether the method succeeded or not.
      */
     public boolean setCallbacks(Callbacks callbacks) {
-        if (mCallbacks == callbacks) {
+        if (mDisplayPowerControllerCallbacks == callbacks) {
             return false;
         }
-        mCallbacks = callbacks;
+        mDisplayPowerControllerCallbacks = callbacks;
         return true;
     }
 
@@ -321,7 +346,7 @@
         writer.println("DisplayWhiteBalanceController");
         writer.println("  mLoggingEnabled=" + mLoggingEnabled);
         writer.println("  mEnabled=" + mEnabled);
-        writer.println("  mCallbacks=" + mCallbacks);
+        writer.println("  mDisplayPowerControllerCallbacks=" + mDisplayPowerControllerCallbacks);
         mBrightnessSensor.dump(writer);
         mBrightnessFilter.dump(writer);
         mColorTemperatureSensor.dump(writer);
@@ -336,6 +361,8 @@
         writer.println("  mAmbientColorTemperatureOverride=" + mAmbientColorTemperatureOverride);
         writer.println("  mAmbientToDisplayColorTemperatureSpline="
                 + mAmbientToDisplayColorTemperatureSpline);
+        writer.println("  mStrongAmbientToDisplayColorTemperatureSpline="
+                + mStrongAmbientToDisplayColorTemperatureSpline);
         writer.println("  mLowLightAmbientBrightnessToBiasSpline="
                 + mLowLightAmbientBrightnessToBiasSpline);
         writer.println("  mHighLightAmbientBrightnessToBiasSpline="
@@ -364,9 +391,20 @@
         float ambientColorTemperature = mColorTemperatureFilter.getEstimate(time);
         mLatestAmbientColorTemperature = ambientColorTemperature;
 
-        if (mAmbientToDisplayColorTemperatureSpline != null && ambientColorTemperature != -1.0f) {
-            ambientColorTemperature =
-                mAmbientToDisplayColorTemperatureSpline.interpolate(ambientColorTemperature);
+        if (mStrongModeEnabled) {
+            if (mStrongAmbientToDisplayColorTemperatureSpline != null
+                    && ambientColorTemperature != -1.0f) {
+                ambientColorTemperature =
+                        mStrongAmbientToDisplayColorTemperatureSpline.interpolate(
+                                ambientColorTemperature);
+            }
+        } else {
+            if (mAmbientToDisplayColorTemperatureSpline != null
+                    && ambientColorTemperature != -1.0f) {
+                ambientColorTemperature =
+                        mAmbientToDisplayColorTemperatureSpline.interpolate(
+                                ambientColorTemperature);
+            }
         }
 
         float ambientBrightness = mBrightnessFilter.getEstimate(time);
@@ -409,8 +447,8 @@
             Slog.d(TAG, "pending ambient color temperature: " + ambientColorTemperature);
         }
         mPendingAmbientColorTemperature = ambientColorTemperature;
-        if (mCallbacks != null) {
-            mCallbacks.updateWhiteBalance();
+        if (mDisplayPowerControllerCallbacks != null) {
+            mDisplayPowerControllerCallbacks.updateWhiteBalance();
         }
     }
 
diff --git a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java
index a72b1ed..07821b0 100644
--- a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java
+++ b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java
@@ -87,15 +87,22 @@
                 .config_displayWhiteBalanceHighLightAmbientColorTemperature);
         final float[] ambientColorTemperatures = getFloatArray(resources,
                 com.android.internal.R.array.config_displayWhiteBalanceAmbientColorTemperatures);
-        final float[] displayColorTempeartures = getFloatArray(resources,
+        final float[] displayColorTemperatures = getFloatArray(resources,
                 com.android.internal.R.array.config_displayWhiteBalanceDisplayColorTemperatures);
+        final float[] strongAmbientColorTemperatures = getFloatArray(resources,
+                com.android.internal.R.array
+                .config_displayWhiteBalanceStrongAmbientColorTemperatures);
+        final float[] strongDisplayColorTemperatures = getFloatArray(resources,
+                com.android.internal.R.array
+                .config_displayWhiteBalanceStrongDisplayColorTemperatures);
         final DisplayWhiteBalanceController controller = new DisplayWhiteBalanceController(
                 brightnessSensor, brightnessFilter, colorTemperatureSensor, colorTemperatureFilter,
                 throttler, displayWhiteBalanceLowLightAmbientBrightnesses,
                 displayWhiteBalanceLowLightAmbientBiases, lowLightAmbientColorTemperature,
                 displayWhiteBalanceHighLightAmbientBrightnesses,
                 displayWhiteBalanceHighLightAmbientBiases, highLightAmbientColorTemperature,
-                ambientColorTemperatures, displayColorTempeartures);
+                ambientColorTemperatures, displayColorTemperatures, strongAmbientColorTemperatures,
+                strongDisplayColorTemperatures);
         brightnessSensor.setCallbacks(controller);
         colorTemperatureSensor.setCallbacks(controller);
         return controller;
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 611b288..f0a6af3 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -416,13 +416,14 @@
         mCurrentDreamCanDoze = canDoze;
         mCurrentDreamUserId = userId;
 
+        if (!mCurrentDreamName.equals(mAmbientDisplayComponent)) {
+            mUiEventLogger.log(DreamManagerEvent.DREAM_START);
+        }
+
         PowerManager.WakeLock wakeLock = mPowerManager
                 .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "startDream");
         mHandler.post(wakeLock.wrap(() -> {
             mAtmInternal.notifyDreamStateChanged(true);
-            if (!mCurrentDreamName.equals(mAmbientDisplayComponent)) {
-                mUiEventLogger.log(DreamManagerEvent.DREAM_START);
-            }
             mController.startDream(newToken, name, isTest, canDoze, userId, wakeLock,
                     mDreamOverlayServiceName);
         }));
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 8f05130..2d2edfa 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -20,6 +20,7 @@
 import static android.Manifest.permission.MANAGE_BIOMETRIC;
 import static android.Manifest.permission.READ_CONTACTS;
 import static android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS;
+import static android.Manifest.permission.SET_INITIAL_LOCK;
 import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_DETAIL;
 import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_MESSAGE;
 import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_TITLE;
@@ -1650,9 +1651,13 @@
                     "This operation requires secure lock screen feature");
         }
         if (!hasPermission(PERMISSION) && !hasPermission(SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS)) {
-            throw new SecurityException(
-                    "setLockCredential requires SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS or "
-                            + PERMISSION);
+            if (hasPermission(SET_INITIAL_LOCK) && savedCredential.isNone()) {
+                // SET_INITIAL_LOCK can only be used if credential is not set.
+            } else {
+                throw new SecurityException(
+                        "setLockCredential requires SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS or "
+                                + PERMISSION);
+            }
         }
 
         final long identity = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 303ab46..7f997df 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -677,9 +677,9 @@
         UserRecord userRecord = routerRecord.mUserRecord;
         userRecord.mRouterRecords.remove(routerRecord);
         routerRecord.mUserRecord.mHandler.sendMessage(
-                obtainMessage(UserHandler::notifyPreferredFeaturesChangedToManagers,
+                obtainMessage(UserHandler::notifyDiscoveryPreferenceChangedToManagers,
                         routerRecord.mUserRecord.mHandler,
-                        routerRecord.mPackageName, /* preferredFeatures=*/ null));
+                        routerRecord.mPackageName, null));
         userRecord.mHandler.sendMessage(
                 obtainMessage(UserHandler::updateDiscoveryPreferenceOnHandler,
                         userRecord.mHandler));
@@ -694,10 +694,10 @@
         }
         routerRecord.mDiscoveryPreference = discoveryRequest;
         routerRecord.mUserRecord.mHandler.sendMessage(
-                obtainMessage(UserHandler::notifyPreferredFeaturesChangedToManagers,
+                obtainMessage(UserHandler::notifyDiscoveryPreferenceChangedToManagers,
                         routerRecord.mUserRecord.mHandler,
                         routerRecord.mPackageName,
-                        routerRecord.mDiscoveryPreference.getPreferredFeatures()));
+                        routerRecord.mDiscoveryPreference));
         routerRecord.mUserRecord.mHandler.sendMessage(
                 obtainMessage(UserHandler::updateDiscoveryPreferenceOnHandler,
                         routerRecord.mUserRecord.mHandler));
@@ -921,7 +921,7 @@
             // TODO: UserRecord <-> routerRecord, why do they reference each other?
             // How about removing mUserRecord from routerRecord?
             routerRecord.mUserRecord.mHandler.sendMessage(
-                    obtainMessage(UserHandler::notifyPreferredFeaturesChangedToManager,
+                    obtainMessage(UserHandler::notifyDiscoveryPreferenceChangedToManager,
                         routerRecord.mUserRecord.mHandler, routerRecord, manager));
         }
 
@@ -2118,19 +2118,19 @@
             }
         }
 
-        private void notifyPreferredFeaturesChangedToManager(@NonNull RouterRecord routerRecord,
+        private void notifyDiscoveryPreferenceChangedToManager(@NonNull RouterRecord routerRecord,
                 @NonNull IMediaRouter2Manager manager) {
             try {
-                manager.notifyPreferredFeaturesChanged(routerRecord.mPackageName,
-                        routerRecord.mDiscoveryPreference.getPreferredFeatures());
+                manager.notifyDiscoveryPreferenceChanged(routerRecord.mPackageName,
+                        routerRecord.mDiscoveryPreference);
             } catch (RemoteException ex) {
                 Slog.w(TAG, "Failed to notify preferred features changed."
                         + " Manager probably died.", ex);
             }
         }
 
-        private void notifyPreferredFeaturesChangedToManagers(@NonNull String routerPackageName,
-                @Nullable List<String> preferredFeatures) {
+        private void notifyDiscoveryPreferenceChangedToManagers(@NonNull String routerPackageName,
+                @Nullable RouteDiscoveryPreference discoveryPreference) {
             MediaRouter2ServiceImpl service = mServiceRef.get();
             if (service == null) {
                 return;
@@ -2143,7 +2143,8 @@
             }
             for (IMediaRouter2Manager manager : managers) {
                 try {
-                    manager.notifyPreferredFeaturesChanged(routerPackageName, preferredFeatures);
+                    manager.notifyDiscoveryPreferenceChanged(routerPackageName,
+                            discoveryPreference);
                 } catch (RemoteException ex) {
                     Slog.w(TAG, "Failed to notify preferred features changed."
                             + " Manager probably died.", ex);
diff --git a/services/core/java/com/android/server/notification/ZenModeFiltering.java b/services/core/java/com/android/server/notification/ZenModeFiltering.java
index b186f61..29aad63 100644
--- a/services/core/java/com/android/server/notification/ZenModeFiltering.java
+++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java
@@ -24,11 +24,14 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.media.AudioAttributes;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.provider.Settings.Global;
 import android.service.notification.ZenModeConfig;
 import android.telecom.TelecomManager;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.TelephonyManager;
 import android.util.ArrayMap;
 import android.util.Slog;
 
@@ -36,6 +39,8 @@
 import com.android.internal.util.NotificationMessagingUtil;
 
 import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
 import java.util.Date;
 
 public class ZenModeFiltering {
@@ -64,13 +69,22 @@
         pw.print(prefix); pw.print("RepeatCallers.mThresholdMinutes=");
         pw.println(REPEAT_CALLERS.mThresholdMinutes);
         synchronized (REPEAT_CALLERS) {
-            if (!REPEAT_CALLERS.mCalls.isEmpty()) {
-                pw.print(prefix); pw.println("RepeatCallers.mCalls=");
-                for (int i = 0; i < REPEAT_CALLERS.mCalls.size(); i++) {
+            if (!REPEAT_CALLERS.mTelCalls.isEmpty()) {
+                pw.print(prefix); pw.println("RepeatCallers.mTelCalls=");
+                for (int i = 0; i < REPEAT_CALLERS.mTelCalls.size(); i++) {
                     pw.print(prefix); pw.print("  ");
-                    pw.print(REPEAT_CALLERS.mCalls.keyAt(i));
+                    pw.print(REPEAT_CALLERS.mTelCalls.keyAt(i));
                     pw.print(" at ");
-                    pw.println(ts(REPEAT_CALLERS.mCalls.valueAt(i)));
+                    pw.println(ts(REPEAT_CALLERS.mTelCalls.valueAt(i)));
+                }
+            }
+            if (!REPEAT_CALLERS.mOtherCalls.isEmpty()) {
+                pw.print(prefix); pw.println("RepeatCallers.mOtherCalls=");
+                for (int i = 0; i < REPEAT_CALLERS.mOtherCalls.size(); i++) {
+                    pw.print(prefix); pw.print("  ");
+                    pw.print(REPEAT_CALLERS.mOtherCalls.keyAt(i));
+                    pw.print(" at ");
+                    pw.println(ts(REPEAT_CALLERS.mOtherCalls.valueAt(i)));
                 }
             }
         }
@@ -330,34 +344,39 @@
     }
 
     private static class RepeatCallers {
-        // Person : time
-        private final ArrayMap<String, Long> mCalls = new ArrayMap<>();
+        // We keep a separate map per uri scheme to do more generous number-matching
+        // handling on telephone numbers specifically. For other inputs, we
+        // simply match directly on the string.
+        private final ArrayMap<String, Long> mTelCalls = new ArrayMap<>();
+        private final ArrayMap<String, Long> mOtherCalls = new ArrayMap<>();
         private int mThresholdMinutes;
 
         private synchronized void recordCall(Context context, Bundle extras) {
             setThresholdMinutes(context);
             if (mThresholdMinutes <= 0 || extras == null) return;
-            final String peopleString = peopleString(extras);
-            if (peopleString == null) return;
+            final String[] extraPeople = ValidateNotificationPeople.getExtraPeople(extras);
+            if (extraPeople == null || extraPeople.length == 0) return;
             final long now = System.currentTimeMillis();
-            cleanUp(mCalls, now);
-            mCalls.put(peopleString, now);
+            cleanUp(mTelCalls, now);
+            cleanUp(mOtherCalls, now);
+            recordCallers(extraPeople, now);
         }
 
         private synchronized boolean isRepeat(Context context, Bundle extras) {
             setThresholdMinutes(context);
             if (mThresholdMinutes <= 0 || extras == null) return false;
-            final String peopleString = peopleString(extras);
-            if (peopleString == null) return false;
+            final String[] extraPeople = ValidateNotificationPeople.getExtraPeople(extras);
+            if (extraPeople == null || extraPeople.length == 0) return false;
             final long now = System.currentTimeMillis();
-            cleanUp(mCalls, now);
-            return mCalls.containsKey(peopleString);
+            cleanUp(mTelCalls, now);
+            cleanUp(mOtherCalls, now);
+            return checkCallers(context, extraPeople);
         }
 
         private synchronized void cleanUp(ArrayMap<String, Long> calls, long now) {
             final int N = calls.size();
             for (int i = N - 1; i >= 0; i--) {
-                final long time = mCalls.valueAt(i);
+                final long time = calls.valueAt(i);
                 if (time > now || (now - time) > mThresholdMinutes * 1000 * 60) {
                     calls.removeAt(i);
                 }
@@ -367,10 +386,16 @@
         // Clean up all calls that occurred after the given time.
         // Used only for tests, to clean up after testing.
         private synchronized void cleanUpCallsAfter(long timeThreshold) {
-            for (int i = mCalls.size() - 1; i >= 0; i--) {
-                final long time = mCalls.valueAt(i);
+            for (int i = mTelCalls.size() - 1; i >= 0; i--) {
+                final long time = mTelCalls.valueAt(i);
                 if (time > timeThreshold) {
-                    mCalls.removeAt(i);
+                    mTelCalls.removeAt(i);
+                }
+            }
+            for (int j = mOtherCalls.size() - 1; j >= 0; j--) {
+                final long time = mOtherCalls.valueAt(j);
+                if (time > timeThreshold) {
+                    mOtherCalls.removeAt(j);
                 }
             }
         }
@@ -382,21 +407,65 @@
             }
         }
 
-        private static String peopleString(Bundle extras) {
-            final String[] extraPeople = ValidateNotificationPeople.getExtraPeople(extras);
-            if (extraPeople == null || extraPeople.length == 0) return null;
-            final StringBuilder sb = new StringBuilder();
-            for (int i = 0; i < extraPeople.length; i++) {
-                String extraPerson = extraPeople[i];
-                if (extraPerson == null) continue;
-                extraPerson = extraPerson.trim();
-                if (extraPerson.isEmpty()) continue;
-                if (sb.length() > 0) {
-                    sb.append('|');
+        private synchronized void recordCallers(String[] people, long now) {
+            for (int i = 0; i < people.length; i++) {
+                String person = people[i];
+                if (person == null) continue;
+                final Uri uri = Uri.parse(person);
+                if ("tel".equals(uri.getScheme())) {
+                    String tel = uri.getSchemeSpecificPart();
+                    // while ideally we should not need to do this, sometimes we have seen tel
+                    // numbers given in a url-encoded format
+                    try {
+                        tel = URLDecoder.decode(tel, "UTF-8");
+                    } catch (UnsupportedEncodingException e) {
+                        // ignore, keep the original tel string
+                        Slog.w(TAG, "unsupported encoding in tel: uri input");
+                    }
+                    mTelCalls.put(tel, now);
+                } else {
+                    // for non-tel calls, store the entire string, uri-component and all
+                    mOtherCalls.put(person, now);
                 }
-                sb.append(extraPerson);
             }
-            return sb.length() == 0 ? null : sb.toString();
+        }
+
+        private synchronized boolean checkCallers(Context context, String[] people) {
+            // get the default country code for checking telephone numbers
+            final String defaultCountryCode =
+                    context.getSystemService(TelephonyManager.class).getNetworkCountryIso();
+            for (int i = 0; i < people.length; i++) {
+                String person = people[i];
+                if (person == null) continue;
+                final Uri uri = Uri.parse(person);
+                if ("tel".equals(uri.getScheme())) {
+                    String number = uri.getSchemeSpecificPart();
+                    if (mTelCalls.containsKey(number)) {
+                        // check directly via map first
+                        return true;
+                    } else {
+                        // see if a number that matches via areSameNumber exists
+                        String numberToCheck = number;
+                        try {
+                            numberToCheck = URLDecoder.decode(number, "UTF-8");
+                        } catch (UnsupportedEncodingException e) {
+                            // ignore, continue to use the original string
+                            Slog.w(TAG, "unsupported encoding in tel: uri input");
+                        }
+                        for (String prev : mTelCalls.keySet()) {
+                            if (PhoneNumberUtils.areSamePhoneNumber(
+                                    numberToCheck, prev, defaultCountryCode)) {
+                                return true;
+                            }
+                        }
+                    }
+                } else {
+                    if (mOtherCalls.containsKey(person)) {
+                        return true;
+                    }
+                }
+            }
+            return false;
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java b/services/core/java/com/android/server/pm/InitAppsHelper.java
similarity index 64%
rename from services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
rename to services/core/java/com/android/server/pm/InitAppsHelper.java
index 06405ae..2fda02d 100644
--- a/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InitAppsHelper.java
@@ -32,6 +32,7 @@
 import static com.android.server.pm.PackageManagerService.TAG;
 import static com.android.server.pm.pkg.parsing.ParsingPackageUtils.PARSE_FRAMEWORK_RES_SPLITS;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.pm.parsing.ApkLiteParseUtils;
 import android.os.Environment;
@@ -61,14 +62,25 @@
  * further cleanup and eventually all the installation/scanning related logic will go to another
  * class.
  */
-final class InitAndSystemPackageHelper {
+final class InitAppsHelper {
     private final PackageManagerService mPm;
-
     private final List<ScanPartition> mDirsToScanAsSystem;
     private final int mScanFlags;
     private final int mSystemParseFlags;
     private final int mSystemScanFlags;
     private final InstallPackageHelper mInstallPackageHelper;
+    private final ApexManager mApexManager;
+    private final PackageParser2 mPackageParser;
+    private final ExecutorService mExecutorService;
+    /* Tracks how long system scan took */
+    private long mSystemScanTime;
+    /* Track of the number of cached system apps */
+    private int mCachedSystemApps;
+    /* Track of the number of system apps */
+    private int mSystemPackagesCount;
+    private final boolean mIsDeviceUpgrading;
+    private final boolean mIsOnlyCoreApps;
+    private final List<ScanPartition> mSystemPartitions;
 
     /**
      * Tracks new system packages [received in an OTA] that we expect to
@@ -76,21 +88,34 @@
      * are package location.
      */
     private final ArrayMap<String, File> mExpectingBetter = new ArrayMap<>();
+    /* Tracks of any system packages that no longer exist that needs to be pruned. */
+    private final List<String> mPossiblyDeletedUpdatedSystemApps = new ArrayList<>();
+    // Tracks of stub packages that must either be replaced with full versions in the /data
+    // partition or be disabled.
+    private final List<String> mStubSystemApps = new ArrayList<>();
 
     // TODO(b/198166813): remove PMS dependency
-    InitAndSystemPackageHelper(PackageManagerService pm) {
+    InitAppsHelper(PackageManagerService pm, ApexManager apexManager,
+            InstallPackageHelper installPackageHelper, PackageParser2 packageParser,
+            List<ScanPartition> systemPartitions) {
         mPm = pm;
-        mInstallPackageHelper = new InstallPackageHelper(pm);
+        mApexManager = apexManager;
+        mInstallPackageHelper = installPackageHelper;
+        mPackageParser = packageParser;
+        mSystemPartitions = systemPartitions;
         mDirsToScanAsSystem = getSystemScanPartitions();
+        mIsDeviceUpgrading = mPm.isDeviceUpgrading();
+        mIsOnlyCoreApps = mPm.isOnlyCoreApps();
         // Set flag to monitor and not change apk file paths when scanning install directories.
         int scanFlags = SCAN_BOOTING | SCAN_INITIAL;
-        if (mPm.isDeviceUpgrading() || mPm.isFirstBoot()) {
+        if (mIsDeviceUpgrading || mPm.isFirstBoot()) {
             mScanFlags = scanFlags | SCAN_FIRST_BOOT_OR_UPGRADE;
         } else {
             mScanFlags = scanFlags;
         }
         mSystemParseFlags = mPm.getDefParseFlags() | ParsingPackageUtils.PARSE_IS_SYSTEM_DIR;
         mSystemScanFlags = scanFlags | SCAN_AS_SYSTEM;
+        mExecutorService = ParallelPackageParser.makeExecutorService();
     }
 
     private List<File> getFrameworkResApkSplitFiles() {
@@ -118,7 +143,7 @@
 
     private List<ScanPartition> getSystemScanPartitions() {
         final List<ScanPartition> scanPartitions = new ArrayList<>();
-        scanPartitions.addAll(mPm.mInjector.getSystemPartitions());
+        scanPartitions.addAll(mSystemPartitions);
         scanPartitions.addAll(getApexScanPartitions());
         Slog.d(TAG, "Directories scanned as system partitions: " + scanPartitions);
         return scanPartitions;
@@ -126,8 +151,7 @@
 
     private List<ScanPartition> getApexScanPartitions() {
         final List<ScanPartition> scanPartitions = new ArrayList<>();
-        final List<ApexManager.ActiveApexInfo> activeApexInfos =
-                mPm.mApexManager.getActiveApexInfos();
+        final List<ApexManager.ActiveApexInfo> activeApexInfos = mApexManager.getActiveApexInfos();
         for (int i = 0; i < activeApexInfos.size(); i++) {
             final ScanPartition scanPartition = resolveApexToScanPartition(activeApexInfos.get(i));
             if (scanPartition != null) {
@@ -144,117 +168,133 @@
             if (apexInfo.preInstalledApexPath.getAbsolutePath().equals(
                     sp.getFolder().getAbsolutePath())
                     || apexInfo.preInstalledApexPath.getAbsolutePath().startsWith(
-                        sp.getFolder().getAbsolutePath() + File.separator)) {
+                    sp.getFolder().getAbsolutePath() + File.separator)) {
                 return new ScanPartition(apexInfo.apexDirectory, sp, SCAN_AS_APK_IN_APEX);
             }
         }
         return null;
     }
 
-    public OverlayConfig initPackages(
-            WatchedArrayMap<String, PackageSetting> packageSettings, int[] userIds,
-            long startTime) {
-        PackageParser2 packageParser = mPm.mInjector.getScanningCachingPackageParser();
-
-        ExecutorService executorService = ParallelPackageParser.makeExecutorService();
+    /**
+     * Install apps from system dirs.
+     */
+    @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
+    public OverlayConfig initSystemApps(WatchedArrayMap<String, PackageSetting> packageSettings,
+            int[] userIds, long startTime) {
         // Prepare apex package info before scanning APKs, this information is needed when
         // scanning apk in apex.
-        mPm.mApexManager.scanApexPackagesTraced(packageParser, executorService);
+        mApexManager.scanApexPackagesTraced(mPackageParser, mExecutorService);
 
-        scanSystemDirs(packageParser, executorService);
+        scanSystemDirs(mPackageParser, mExecutorService);
         // Parse overlay configuration files to set default enable state, mutability, and
         // priority of system overlays.
         final ArrayMap<String, File> apkInApexPreInstalledPaths = new ArrayMap<>();
-        for (ApexManager.ActiveApexInfo apexInfo : mPm.mApexManager.getActiveApexInfos()) {
-            for (String packageName : mPm.mApexManager.getApksInApex(apexInfo.apexModuleName)) {
+        for (ApexManager.ActiveApexInfo apexInfo : mApexManager.getActiveApexInfos()) {
+            for (String packageName : mApexManager.getApksInApex(apexInfo.apexModuleName)) {
                 apkInApexPreInstalledPaths.put(packageName, apexInfo.preInstalledApexPath);
             }
         }
-        OverlayConfig overlayConfig = OverlayConfig.initializeSystemInstance(
+        final OverlayConfig overlayConfig = OverlayConfig.initializeSystemInstance(
                 consumer -> mPm.forEachPackage(
                         pkg -> consumer.accept(pkg, pkg.isSystem(),
-                          apkInApexPreInstalledPaths.get(pkg.getPackageName()))));
-        // Prune any system packages that no longer exist.
-        final List<String> possiblyDeletedUpdatedSystemApps = new ArrayList<>();
-        // Stub packages must either be replaced with full versions in the /data
-        // partition or be disabled.
-        final List<String> stubSystemApps = new ArrayList<>();
+                                apkInApexPreInstalledPaths.get(pkg.getPackageName()))));
 
-        if (!mPm.isOnlyCoreApps()) {
+        if (!mIsOnlyCoreApps) {
             // do this first before mucking with mPackages for the "expecting better" case
-            updateStubSystemAppsList(stubSystemApps);
+            updateStubSystemAppsList(mStubSystemApps);
             mInstallPackageHelper.prepareSystemPackageCleanUp(packageSettings,
-                    possiblyDeletedUpdatedSystemApps, mExpectingBetter, userIds);
+                    mPossiblyDeletedUpdatedSystemApps, mExpectingBetter, userIds);
         }
 
-        final int cachedSystemApps = PackageCacher.sCachedPackageReadCount.get();
+        logSystemAppsScanningTime(startTime);
+        return overlayConfig;
+    }
+
+    @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
+    private void logSystemAppsScanningTime(long startTime) {
+        mCachedSystemApps = PackageCacher.sCachedPackageReadCount.get();
 
         // Remove any shared userIDs that have no associated packages
         mPm.mSettings.pruneSharedUsersLPw();
-        final long systemScanTime = SystemClock.uptimeMillis() - startTime;
-        final int systemPackagesCount = mPm.mPackages.size();
-        Slog.i(TAG, "Finished scanning system apps. Time: " + systemScanTime
-                + " ms, packageCount: " + systemPackagesCount
+        mSystemScanTime = SystemClock.uptimeMillis() - startTime;
+        mSystemPackagesCount = mPm.mPackages.size();
+        Slog.i(TAG, "Finished scanning system apps. Time: " + mSystemScanTime
+                + " ms, packageCount: " + mSystemPackagesCount
                 + " , timePerPackage: "
-                + (systemPackagesCount == 0 ? 0 : systemScanTime / systemPackagesCount)
-                + " , cached: " + cachedSystemApps);
-        if (mPm.isDeviceUpgrading() && systemPackagesCount > 0) {
+                + (mSystemPackagesCount == 0 ? 0 : mSystemScanTime / mSystemPackagesCount)
+                + " , cached: " + mCachedSystemApps);
+        if (mIsDeviceUpgrading && mSystemPackagesCount > 0) {
             //CHECKSTYLE:OFF IndentationCheck
             FrameworkStatsLog.write(FrameworkStatsLog.BOOT_TIME_EVENT_DURATION_REPORTED,
                     BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_SYSTEM_APP_AVG_SCAN_TIME,
-                    systemScanTime / systemPackagesCount);
+                    mSystemScanTime / mSystemPackagesCount);
             //CHECKSTYLE:ON IndentationCheck
         }
+    }
 
-        if (!mPm.isOnlyCoreApps()) {
+    /**
+     * Install apps/updates from data dir and fix system apps that are affected.
+     */
+    @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
+    public void initNonSystemApps(@NonNull int[] userIds, long startTime) {
+        if (!mIsOnlyCoreApps) {
             EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,
                     SystemClock.uptimeMillis());
             scanDirTracedLI(mPm.getAppInstallDir(), /* frameworkSplits= */ null, 0,
-                    mScanFlags | SCAN_REQUIRE_KNOWN, 0,
-                    packageParser, executorService);
-
+                    mScanFlags | SCAN_REQUIRE_KNOWN,
+                    mPackageParser, mExecutorService);
         }
 
-        List<Runnable> unfinishedTasks = executorService.shutdownNow();
+        List<Runnable> unfinishedTasks = mExecutorService.shutdownNow();
         if (!unfinishedTasks.isEmpty()) {
             throw new IllegalStateException("Not all tasks finished before calling close: "
                     + unfinishedTasks);
         }
-
-        if (!mPm.isOnlyCoreApps()) {
-            mInstallPackageHelper.cleanupDisabledPackageSettings(possiblyDeletedUpdatedSystemApps,
-                    userIds, mScanFlags);
-            mInstallPackageHelper.checkExistingBetterPackages(mExpectingBetter,
-                    stubSystemApps, mSystemScanFlags, mSystemParseFlags);
-
-            // Uncompress and install any stubbed system applications.
-            // This must be done last to ensure all stubs are replaced or disabled.
-            mInstallPackageHelper.installSystemStubPackages(stubSystemApps, mScanFlags);
-
-            final int cachedNonSystemApps = PackageCacher.sCachedPackageReadCount.get()
-                    - cachedSystemApps;
-
-            final long dataScanTime = SystemClock.uptimeMillis() - systemScanTime - startTime;
-            final int dataPackagesCount = mPm.mPackages.size() - systemPackagesCount;
-            Slog.i(TAG, "Finished scanning non-system apps. Time: " + dataScanTime
-                    + " ms, packageCount: " + dataPackagesCount
-                    + " , timePerPackage: "
-                    + (dataPackagesCount == 0 ? 0 : dataScanTime / dataPackagesCount)
-                    + " , cached: " + cachedNonSystemApps);
-            if (mPm.isDeviceUpgrading() && dataPackagesCount > 0) {
-                //CHECKSTYLE:OFF IndentationCheck
-                FrameworkStatsLog.write(
-                        FrameworkStatsLog.BOOT_TIME_EVENT_DURATION_REPORTED,
-                        BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_DATA_APP_AVG_SCAN_TIME,
-                        dataScanTime / dataPackagesCount);
-                //CHECKSTYLE:OFF IndentationCheck
-            }
+        if (!mIsOnlyCoreApps) {
+            fixSystemPackages(userIds);
+            logNonSystemAppScanningTime(startTime);
         }
         mExpectingBetter.clear();
-
         mPm.mSettings.pruneRenamedPackagesLPw();
-        packageParser.close();
-        return overlayConfig;
+        mPackageParser.close();
+    }
+
+    /**
+     * Clean up system packages now that some system package updates have been installed from
+     * the data dir. Also install system stub packages as the last step.
+     */
+    @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
+    private void fixSystemPackages(@NonNull int[] userIds) {
+        mInstallPackageHelper.cleanupDisabledPackageSettings(mPossiblyDeletedUpdatedSystemApps,
+                userIds, mScanFlags);
+        mInstallPackageHelper.checkExistingBetterPackages(mExpectingBetter,
+                mStubSystemApps, mSystemScanFlags, mSystemParseFlags);
+
+        // Uncompress and install any stubbed system applications.
+        // This must be done last to ensure all stubs are replaced or disabled.
+        mInstallPackageHelper.installSystemStubPackages(mStubSystemApps, mScanFlags);
+    }
+
+    @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
+    private void logNonSystemAppScanningTime(long startTime) {
+        final int cachedNonSystemApps = PackageCacher.sCachedPackageReadCount.get()
+                - mCachedSystemApps;
+
+        final long dataScanTime = SystemClock.uptimeMillis() - mSystemScanTime - startTime;
+        final int dataPackagesCount = mPm.mPackages.size() - mSystemPackagesCount;
+        Slog.i(TAG, "Finished scanning non-system apps. Time: " + dataScanTime
+                + " ms, packageCount: " + dataPackagesCount
+                + " , timePerPackage: "
+                + (dataPackagesCount == 0 ? 0 : dataScanTime / dataPackagesCount)
+                + " , cached: " + cachedNonSystemApps);
+        if (mIsDeviceUpgrading && dataPackagesCount > 0) {
+            //CHECKSTYLE:OFF IndentationCheck
+            FrameworkStatsLog.write(
+                    FrameworkStatsLog.BOOT_TIME_EVENT_DURATION_REPORTED,
+                    BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_DATA_APP_AVG_SCAN_TIME,
+                    dataScanTime / dataPackagesCount);
+            //CHECKSTYLE:OFF IndentationCheck
+        }
     }
 
     /**
@@ -274,14 +314,14 @@
                 continue;
             }
             scanDirTracedLI(partition.getOverlayFolder(), /* frameworkSplits= */ null,
-                    mSystemParseFlags, mSystemScanFlags | partition.scanFlag, 0,
+                    mSystemParseFlags, mSystemScanFlags | partition.scanFlag,
                     packageParser, executorService);
         }
 
         List<File> frameworkSplits = getFrameworkResApkSplitFiles();
         scanDirTracedLI(frameworkDir, frameworkSplits,
                 mSystemParseFlags | PARSE_FRAMEWORK_RES_SPLITS,
-                mSystemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED, 0,
+                mSystemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED,
                 packageParser, executorService);
         if (!mPm.mPackages.containsKey("android")) {
             throw new IllegalStateException(
@@ -293,11 +333,11 @@
             if (partition.getPrivAppFolder() != null) {
                 scanDirTracedLI(partition.getPrivAppFolder(), /* frameworkSplits= */ null,
                         mSystemParseFlags,
-                        mSystemScanFlags | SCAN_AS_PRIVILEGED | partition.scanFlag, 0,
+                        mSystemScanFlags | SCAN_AS_PRIVILEGED | partition.scanFlag,
                         packageParser, executorService);
             }
             scanDirTracedLI(partition.getAppFolder(), /* frameworkSplits= */ null,
-                    mSystemParseFlags, mSystemScanFlags | partition.scanFlag, 0,
+                    mSystemParseFlags, mSystemScanFlags | partition.scanFlag,
                     packageParser, executorService);
         }
     }
@@ -316,11 +356,11 @@
     @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
     private void scanDirTracedLI(File scanDir, List<File> frameworkSplits,
             final int parseFlags, int scanFlags,
-            long currentTime, PackageParser2 packageParser, ExecutorService executorService) {
+            PackageParser2 packageParser, ExecutorService executorService) {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]");
         try {
             mInstallPackageHelper.installPackagesFromDir(scanDir, frameworkSplits, parseFlags,
-                    scanFlags, currentTime, packageParser, executorService);
+                    scanFlags, packageParser, executorService);
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 50c26f4..83c8472 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -141,7 +141,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.content.F2fsUtils;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
 import com.android.internal.security.VerityUtils;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FrameworkStatsLog;
@@ -2358,26 +2358,26 @@
                 if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
                     // Check for updated system application.
                     if (installedPkg.isSystem()) {
-                        return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
+                        return InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL;
                     } else {
                         // If current upgrade specifies particular preference
                         if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
                             // Application explicitly specified internal.
-                            return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
+                            return InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL;
                         } else if (
                                 installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
                             // App explicitly prefers external. Let policy decide
                         } else {
                             // Prefer previous location
                             if (installedPkg.isExternalStorage()) {
-                                return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
+                                return InstallLocationUtils.RECOMMEND_INSTALL_EXTERNAL;
                             }
-                            return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
+                            return InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL;
                         }
                     }
                 } else {
                     // Invalid install. Return error code
-                    return PackageHelper.RECOMMEND_FAILED_ALREADY_EXISTS;
+                    return InstallLocationUtils.RECOMMEND_FAILED_ALREADY_EXISTS;
                 }
             }
         }
@@ -3030,7 +3030,7 @@
         final RemovePackageHelper removePackageHelper = new RemovePackageHelper(mPm);
         removePackageHelper.removePackageLI(stubPkg, true /*chatty*/);
         try {
-            return scanSystemPackageTracedLI(scanFile, parseFlags, scanFlags, 0, null);
+            return scanSystemPackageTracedLI(scanFile, parseFlags, scanFlags, null);
         } catch (PackageManagerException e) {
             Slog.w(TAG, "Failed to install compressed system package:" + stubPkg.getPackageName(),
                     e);
@@ -3163,7 +3163,7 @@
                         | ParsingPackageUtils.PARSE_IS_SYSTEM_DIR;
         @PackageManagerService.ScanFlags int scanFlags = mPm.getSystemPackageScanFlags(codePath);
         final AndroidPackage pkg = scanSystemPackageTracedLI(
-                        codePath, parseFlags, scanFlags, 0 /*currentTime*/, null);
+                codePath, parseFlags, scanFlags, null);
 
         PackageSetting pkgSetting = mPm.mSettings.getPackageLPr(pkg.getPackageName());
 
@@ -3338,7 +3338,7 @@
                 mRemovePackageHelper.removePackageLI(pkg, true);
                 try {
                     final File codePath = new File(pkg.getPath());
-                    scanSystemPackageTracedLI(codePath, 0, scanFlags, 0, null);
+                    scanSystemPackageTracedLI(codePath, 0, scanFlags, null);
                 } catch (PackageManagerException e) {
                     Slog.e(TAG, "Failed to parse updated, ex-system package: "
                             + e.getMessage());
@@ -3359,7 +3359,7 @@
 
     @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
     public void installPackagesFromDir(File scanDir, List<File> frameworkSplits, int parseFlags,
-            int scanFlags, long currentTime, PackageParser2 packageParser,
+            int scanFlags, PackageParser2 packageParser,
             ExecutorService executorService) {
         final File[] files = scanDir.listFiles();
         if (ArrayUtils.isEmpty(files)) {
@@ -3402,7 +3402,7 @@
                             parseResult.parsedPackage);
                 }
                 try {
-                    addForInitLI(parseResult.parsedPackage, parseFlags, scanFlags, currentTime,
+                    addForInitLI(parseResult.parsedPackage, parseFlags, scanFlags,
                             null);
                 } catch (PackageManagerException e) {
                     errorCode = e.error;
@@ -3465,7 +3465,7 @@
 
             try {
                 final AndroidPackage newPkg = scanSystemPackageTracedLI(
-                        scanFile, reparseFlags, rescanFlags, 0, null);
+                        scanFile, reparseFlags, rescanFlags, null);
                 // We rescanned a stub, add it to the list of stubbed system packages
                 if (newPkg.isStub()) {
                     stubSystemApps.add(packageName);
@@ -3479,14 +3479,14 @@
 
     /**
      *  Traces a package scan.
-     *  @see #scanSystemPackageLI(File, int, int, long, UserHandle)
+     *  @see #scanSystemPackageLI(File, int, int, UserHandle)
      */
     @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
     public AndroidPackage scanSystemPackageTracedLI(File scanFile, final int parseFlags,
-            int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {
+            int scanFlags, UserHandle user) throws PackageManagerException {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanPackage [" + scanFile.toString() + "]");
         try {
-            return scanSystemPackageLI(scanFile, parseFlags, scanFlags, currentTime, user);
+            return scanSystemPackageLI(scanFile, parseFlags, scanFlags, user);
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
@@ -3498,7 +3498,7 @@
      */
     @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
     private AndroidPackage scanSystemPackageLI(File scanFile, int parseFlags, int scanFlags,
-            long currentTime, UserHandle user) throws PackageManagerException {
+            UserHandle user) throws PackageManagerException {
         if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanFile);
 
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");
@@ -3514,7 +3514,7 @@
             PackageManagerService.renameStaticSharedLibraryPackage(parsedPackage);
         }
 
-        return addForInitLI(parsedPackage, parseFlags, scanFlags, currentTime, user);
+        return addForInitLI(parsedPackage, parseFlags, scanFlags, user);
     }
 
     /**
@@ -3533,11 +3533,11 @@
     @GuardedBy({"mPm.mLock", "mPm.mInstallLock"})
     private AndroidPackage addForInitLI(ParsedPackage parsedPackage,
             @ParsingPackageUtils.ParseFlags int parseFlags,
-            @PackageManagerService.ScanFlags int scanFlags, long currentTime,
+            @PackageManagerService.ScanFlags int scanFlags,
             @Nullable UserHandle user) throws PackageManagerException {
 
         final Pair<ScanResult, Boolean> scanResultPair = scanSystemPackageLI(
-                parsedPackage, parseFlags, scanFlags, currentTime, user);
+                parsedPackage, parseFlags, scanFlags, user);
         final ScanResult scanResult = scanResultPair.first;
         boolean shouldHideSystemApp = scanResultPair.second;
         if (scanResult.mSuccess) {
@@ -3715,7 +3715,7 @@
 
     private Pair<ScanResult, Boolean> scanSystemPackageLI(ParsedPackage parsedPackage,
             @ParsingPackageUtils.ParseFlags int parseFlags,
-            @PackageManagerService.ScanFlags int scanFlags, long currentTime,
+            @PackageManagerService.ScanFlags int scanFlags,
             @Nullable UserHandle user) throws PackageManagerException {
         final boolean scanSystemPartition =
                 (parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0;
@@ -3902,7 +3902,7 @@
         }
 
         final ScanResult scanResult = scanPackageNewLI(parsedPackage, parseFlags,
-                scanFlags | SCAN_UPDATE_SIGNATURE, currentTime, user, null);
+                scanFlags | SCAN_UPDATE_SIGNATURE, 0 /* currentTime */, user, null);
         return new Pair<>(scanResult, shouldHideSystemApp);
     }
 
diff --git a/services/core/java/com/android/server/pm/InstallParams.java b/services/core/java/com/android/server/pm/InstallParams.java
index d996fe4..7e845c7 100644
--- a/services/core/java/com/android/server/pm/InstallParams.java
+++ b/services/core/java/com/android/server/pm/InstallParams.java
@@ -35,18 +35,17 @@
 import android.content.pm.PackageManager;
 import android.content.pm.SigningDetails;
 import android.content.pm.parsing.PackageLite;
-import android.os.Environment;
 import android.os.Message;
 import android.os.Trace;
 import android.os.UserHandle;
-import android.os.storage.StorageManager;
 import android.util.ArrayMap;
 import android.util.Pair;
 import android.util.Slog;
 
 import com.android.internal.content.F2fsUtils;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
 import com.android.internal.util.Preconditions;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -142,12 +141,8 @@
      * Only {@link PackageManager#INSTALL_INTERNAL} flag may mutate in
      * {@link #mInstallFlags}
      */
-    private int overrideInstallLocation(PackageInfoLite pkgLite) {
-        final boolean ephemeral = (mInstallFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
-        if (DEBUG_INSTANT && ephemeral) {
-            Slog.v(TAG, "pkgLite for install: " + pkgLite);
-        }
-
+    private int overrideInstallLocation(String packageName, int recommendedInstallLocation,
+            int installLocation) {
         if (mOriginInfo.mStaged) {
             // If we're already staged, we've firmly committed to an install location
             if (mOriginInfo.mFile != null) {
@@ -155,77 +150,35 @@
             } else {
                 throw new IllegalStateException("Invalid stage location");
             }
-        } else if (pkgLite.recommendedInstallLocation
-                == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
-            /*
-             * If we are not staged and have too little free space, try to free cache
-             * before giving up.
-             */
-            // TODO: focus freeing disk space on the target device
-            final StorageManager storage = StorageManager.from(mPm.mContext);
-            final long lowThreshold = storage.getStorageLowBytes(
-                    Environment.getDataDirectory());
-
-            final long sizeBytes = PackageManagerServiceUtils.calculateInstalledSize(
-                    mOriginInfo.mResolvedPath, mPackageAbiOverride);
-            if (sizeBytes >= 0) {
-                synchronized (mPm.mInstallLock) {
-                    try {
-                        mPm.mInstaller.freeCache(null, sizeBytes + lowThreshold, 0);
-                        pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(mPm.mContext,
-                                mPackageLite, mOriginInfo.mResolvedPath, mInstallFlags,
-                                mPackageAbiOverride);
-                    } catch (Installer.InstallerException e) {
-                        Slog.w(TAG, "Failed to free cache", e);
-                    }
-                }
-            }
-
-            /*
-             * The cache free must have deleted the file we downloaded to install.
-             *
-             * TODO: fix the "freeCache" call to not delete the file we care about.
-             */
-            if (pkgLite.recommendedInstallLocation
-                    == PackageHelper.RECOMMEND_FAILED_INVALID_URI) {
-                pkgLite.recommendedInstallLocation =
-                        PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
+        }
+        if (recommendedInstallLocation < 0) {
+            return InstallLocationUtils.getInstallationErrorCode(recommendedInstallLocation);
+        }
+        // Override with defaults if needed.
+        synchronized (mPm.mLock) {
+            // reader
+            AndroidPackage installedPkg = mPm.mPackages.get(packageName);
+            if (installedPkg != null) {
+                // Currently installed package which the new package is attempting to replace
+                recommendedInstallLocation = InstallLocationUtils.installLocationPolicy(
+                        installLocation, recommendedInstallLocation, mInstallFlags,
+                        installedPkg.isSystem(), installedPkg.isExternalStorage());
             }
         }
 
-        int ret = INSTALL_SUCCEEDED;
-        int loc = pkgLite.recommendedInstallLocation;
-        if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION) {
-            ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
-        } else if (loc == PackageHelper.RECOMMEND_FAILED_ALREADY_EXISTS) {
-            ret = PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
-        } else if (loc == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
-            ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
-        } else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_APK) {
-            ret = PackageManager.INSTALL_FAILED_INVALID_APK;
-        } else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_URI) {
-            ret = PackageManager.INSTALL_FAILED_INVALID_URI;
-        } else if (loc == PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE) {
-            ret = PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE;
-        } else {
-            // Override with defaults if needed.
-            loc = mInstallPackageHelper.installLocationPolicy(pkgLite, mInstallFlags);
+        final boolean onInt = (mInstallFlags & PackageManager.INSTALL_INTERNAL) != 0;
 
-            final boolean onInt = (mInstallFlags & PackageManager.INSTALL_INTERNAL) != 0;
-
-            if (!onInt) {
-                // Override install location with flags
-                if (loc == PackageHelper.RECOMMEND_INSTALL_EXTERNAL) {
-                    // Set the flag to install on external media.
-                    mInstallFlags &= ~PackageManager.INSTALL_INTERNAL;
-                } else {
-                    // Make sure the flag for installing on external
-                    // media is unset
-                    mInstallFlags |= PackageManager.INSTALL_INTERNAL;
-                }
+        if (!onInt) {
+            // Override install location with flags
+            if (recommendedInstallLocation == InstallLocationUtils.RECOMMEND_INSTALL_EXTERNAL) {
+                // Set the flag to install on external media.
+                mInstallFlags &= ~PackageManager.INSTALL_INTERNAL;
+            } else {
+                // Make sure the flag for installing on external media is unset
+                mInstallFlags |= PackageManager.INSTALL_INTERNAL;
             }
         }
-        return ret;
+        return INSTALL_SUCCEEDED;
     }
 
     /*
@@ -254,7 +207,21 @@
             }
         }
 
-        mRet = overrideInstallLocation(pkgLite);
+        final boolean ephemeral = (mInstallFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
+        if (DEBUG_INSTANT && ephemeral) {
+            Slog.v(TAG, "pkgLite for install: " + pkgLite);
+        }
+
+        if (!mOriginInfo.mStaged && pkgLite.recommendedInstallLocation
+                == InstallLocationUtils.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
+            // If we are not staged and have too little free space, try to free cache
+            // before giving up.
+            pkgLite.recommendedInstallLocation = mPm.freeCacheForInstallation(
+                    pkgLite.recommendedInstallLocation, mPackageLite,
+                    mOriginInfo.mResolvedPath, mPackageAbiOverride, mInstallFlags);
+        }
+        mRet = overrideInstallLocation(pkgLite.packageName, pkgLite.recommendedInstallLocation,
+                pkgLite.installLocation);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index ccc375f..8465248 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -84,7 +84,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.util.ImageUtils;
@@ -782,7 +782,7 @@
             // If caller requested explicit location, validity check it, otherwise
             // resolve the best internal or adopted location.
             if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
-                if (!PackageHelper.fitsOnInternal(mContext, params)) {
+                if (!InstallLocationUtils.fitsOnInternal(mContext, params)) {
                     throw new IOException("No suitable internal storage available");
                 }
             } else if ((params.installFlags & PackageManager.INSTALL_FORCE_VOLUME_UUID) != 0) {
@@ -796,7 +796,7 @@
                 // requested install flags, delta size, and manifest settings.
                 final long ident = Binder.clearCallingIdentity();
                 try {
-                    params.volumeUuid = PackageHelper.resolveInstallVolume(mContext, params);
+                    params.volumeUuid = InstallLocationUtils.resolveInstallVolume(mContext, params);
                 } finally {
                     Binder.restoreCallingIdentity(ident);
                 }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 390dd3f..7152783 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -143,8 +143,8 @@
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.content.InstallLocationUtils;
 import com.android.internal.content.NativeLibraryHelper;
-import com.android.internal.content.PackageHelper;
 import com.android.internal.messages.nano.SystemMessageProto;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.security.VerityUtils;
@@ -1537,7 +1537,7 @@
             if (stageDir != null && lengthBytes > 0) {
                 mContext.getSystemService(StorageManager.class).allocateBytes(
                         targetPfd.getFileDescriptor(), lengthBytes,
-                        PackageHelper.translateAllocateFlags(params.installFlags));
+                        InstallLocationUtils.translateAllocateFlags(params.installFlags));
             }
 
             if (offsetBytes > 0) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 79cfa06..9d95ead 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -101,6 +101,7 @@
 import android.content.pm.ModuleInfo;
 import android.content.pm.PackageChangeEvent;
 import android.content.pm.PackageInfo;
+import android.content.pm.PackageInfoLite;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.ComponentEnabledSetting;
@@ -129,6 +130,7 @@
 import android.content.pm.VersionedPackage;
 import android.content.pm.dex.IArtManager;
 import android.content.pm.overlay.OverlayPaths;
+import android.content.pm.parsing.PackageLite;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.graphics.Bitmap;
@@ -189,7 +191,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.ResolverActivity;
 import com.android.internal.content.F2fsUtils;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
 import com.android.internal.content.om.OverlayConfig;
 import com.android.internal.telephony.CarrierAppUtils;
 import com.android.internal.util.ArrayUtils;
@@ -942,7 +944,7 @@
     private final BroadcastHelper mBroadcastHelper;
     private final RemovePackageHelper mRemovePackageHelper;
     private final DeletePackageHelper mDeletePackageHelper;
-    private final InitAndSystemPackageHelper mInitAndSystemPackageHelper;
+    private final InitAppsHelper mInitAppsHelper;
     private final AppDataHelper mAppDataHelper;
     private final InstallPackageHelper mInstallPackageHelper;
     private final PreferredActivityHelper mPreferredActivityHelper;
@@ -1687,7 +1689,7 @@
         mAppDataHelper = testParams.appDataHelper;
         mInstallPackageHelper = testParams.installPackageHelper;
         mRemovePackageHelper = testParams.removePackageHelper;
-        mInitAndSystemPackageHelper = testParams.initAndSystemPackageHelper;
+        mInitAppsHelper = testParams.initAndSystemPackageHelper;
         mDeletePackageHelper = testParams.deletePackageHelper;
         mPreferredActivityHelper = testParams.preferredActivityHelper;
         mResolveIntentHelper = testParams.resolveIntentHelper;
@@ -1834,7 +1836,8 @@
         mAppDataHelper = new AppDataHelper(this);
         mInstallPackageHelper = new InstallPackageHelper(this, mAppDataHelper);
         mRemovePackageHelper = new RemovePackageHelper(this, mAppDataHelper);
-        mInitAndSystemPackageHelper = new InitAndSystemPackageHelper(this);
+        mInitAppsHelper = new InitAppsHelper(this, mApexManager, mInstallPackageHelper,
+                mInjector.getScanningCachingPackageParser(), mInjector.getSystemPartitions());
         mDeletePackageHelper = new DeletePackageHelper(this, mRemovePackageHelper,
                 mAppDataHelper);
         mSharedLibraries.setDeletePackageHelper(mDeletePackageHelper);
@@ -1964,8 +1967,8 @@
                     mIsEngBuild, mIsUserDebugBuild, mIncrementalVersion);
 
             final int[] userIds = mUserManager.getUserIds();
-            mOverlayConfig = mInitAndSystemPackageHelper.initPackages(packageSettings,
-                    userIds, startTime);
+            mOverlayConfig = mInitAppsHelper.initSystemApps(packageSettings, userIds, startTime);
+            mInitAppsHelper.initNonSystemApps(userIds, startTime);
 
             // Resolve the storage manager.
             mStorageManagerPackage = getStorageManagerPackageName();
@@ -2901,6 +2904,36 @@
         throw new IOException("Failed to free " + bytes + " on storage device at " + file);
     }
 
+    int freeCacheForInstallation(int recommendedInstallLocation, PackageLite pkgLite,
+            String resolvedPath, String mPackageAbiOverride, int installFlags) {
+        // TODO: focus freeing disk space on the target device
+        final StorageManager storage = StorageManager.from(mContext);
+        final long lowThreshold = storage.getStorageLowBytes(Environment.getDataDirectory());
+
+        final long sizeBytes = PackageManagerServiceUtils.calculateInstalledSize(resolvedPath,
+                mPackageAbiOverride);
+        if (sizeBytes >= 0) {
+            synchronized (mInstallLock) {
+                try {
+                    mInstaller.freeCache(null, sizeBytes + lowThreshold, 0);
+                    PackageInfoLite pkgInfoLite = PackageManagerServiceUtils.getMinimalPackageInfo(
+                            mContext, pkgLite, resolvedPath, installFlags,
+                            mPackageAbiOverride);
+                    // The cache free must have deleted the file we downloaded to install.
+                    if (pkgInfoLite.recommendedInstallLocation
+                            == InstallLocationUtils.RECOMMEND_FAILED_INVALID_URI) {
+                        pkgInfoLite.recommendedInstallLocation =
+                                InstallLocationUtils.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
+                    }
+                    return pkgInfoLite.recommendedInstallLocation;
+                } catch (Installer.InstallerException e) {
+                    Slog.w(TAG, "Failed to free cache", e);
+                }
+            }
+        }
+        return recommendedInstallLocation;
+    }
+
     /**
      * Update given flags when being used to request {@link PackageInfo}.
      */
@@ -3676,7 +3709,7 @@
 
         // Before everything else, see whether we need to fstrim.
         try {
-            IStorageManager sm = PackageHelper.getStorageManager();
+            IStorageManager sm = InstallLocationUtils.getStorageManager();
             if (sm != null) {
                 boolean doTrim = false;
                 final long interval = android.provider.Settings.Global.getLong(
@@ -6595,8 +6628,9 @@
         if (getInstallLocation() == loc) {
             return true;
         }
-        if (loc == PackageHelper.APP_INSTALL_AUTO || loc == PackageHelper.APP_INSTALL_INTERNAL
-                || loc == PackageHelper.APP_INSTALL_EXTERNAL) {
+        if (loc == InstallLocationUtils.APP_INSTALL_AUTO
+                || loc == InstallLocationUtils.APP_INSTALL_INTERNAL
+                || loc == InstallLocationUtils.APP_INSTALL_EXTERNAL) {
             android.provider.Settings.Global.putInt(mContext.getContentResolver(),
                     android.provider.Settings.Global.DEFAULT_INSTALL_LOCATION, loc);
             return true;
@@ -6609,7 +6643,7 @@
         // allow instant app access
         return android.provider.Settings.Global.getInt(mContext.getContentResolver(),
                 android.provider.Settings.Global.DEFAULT_INSTALL_LOCATION,
-                PackageHelper.APP_INSTALL_AUTO);
+                InstallLocationUtils.APP_INSTALL_AUTO);
     }
 
     /** Called by UserManagerService */
@@ -8643,7 +8677,7 @@
     }
 
     boolean isExpectingBetter(String packageName) {
-        return mInitAndSystemPackageHelper.isExpectingBetter(packageName);
+        return mInitAppsHelper.isExpectingBetter(packageName);
     }
 
     int getDefParseFlags() {
@@ -8746,13 +8780,12 @@
     }
 
     boolean isOverlayMutable(String packageName) {
-        return (mOverlayConfig != null ? mOverlayConfig
-                : OverlayConfig.getSystemInstance()).isMutable(packageName);
+        return mOverlayConfig.isMutable(packageName);
     }
 
     @ScanFlags int getSystemPackageScanFlags(File codePath) {
         List<ScanPartition> dirsToScanAsSystem =
-                mInitAndSystemPackageHelper.getDirsToScanAsSystem();
+                mInitAppsHelper.getDirsToScanAsSystem();
         @PackageManagerService.ScanFlags int scanFlags = SCAN_AS_SYSTEM;
         for (int i = dirsToScanAsSystem.size() - 1; i >= 0; i--) {
             ScanPartition partition = dirsToScanAsSystem.get(i);
@@ -8770,7 +8803,7 @@
     Pair<Integer, Integer> getSystemPackageRescanFlagsAndReparseFlags(File scanFile,
             int systemScanFlags, int systemParseFlags) {
         List<ScanPartition> dirsToScanAsSystem =
-                mInitAndSystemPackageHelper.getDirsToScanAsSystem();
+                mInitAppsHelper.getDirsToScanAsSystem();
         @ParsingPackageUtils.ParseFlags int reparseFlags = 0;
         @PackageManagerService.ScanFlags int rescanFlags = 0;
         for (int i1 = dirsToScanAsSystem.size() - 1; i1 >= 0; i1--) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index 1caa76d..00ca4ae 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -108,7 +108,7 @@
     public AppDataHelper appDataHelper;
     public InstallPackageHelper installPackageHelper;
     public RemovePackageHelper removePackageHelper;
-    public InitAndSystemPackageHelper initAndSystemPackageHelper;
+    public InitAppsHelper initAndSystemPackageHelper;
     public DeletePackageHelper deletePackageHelper;
     public PreferredActivityHelper preferredActivityHelper;
     public ResolveIntentHelper resolveIntentHelper;
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 19c31e0..d6340b5 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -43,6 +43,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ComponentInfo;
 import android.content.pm.PackageInfoLite;
+import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.pm.PackagePartitions;
 import android.content.pm.ResolveInfo;
@@ -79,8 +80,8 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.content.InstallLocationUtils;
 import com.android.internal.content.NativeLibraryHelper;
-import com.android.internal.content.PackageHelper;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.HexDump;
@@ -805,27 +806,37 @@
         final PackageInfoLite ret = new PackageInfoLite();
         if (packagePath == null || pkg == null) {
             Slog.i(TAG, "Invalid package file " + packagePath);
-            ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
+            ret.recommendedInstallLocation = InstallLocationUtils.RECOMMEND_FAILED_INVALID_APK;
             return ret;
         }
 
         final File packageFile = new File(packagePath);
         final long sizeBytes;
         try {
-            sizeBytes = PackageHelper.calculateInstalledSize(pkg, abiOverride);
+            sizeBytes = InstallLocationUtils.calculateInstalledSize(pkg, abiOverride);
         } catch (IOException e) {
             if (!packageFile.exists()) {
-                ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI;
+                ret.recommendedInstallLocation = InstallLocationUtils.RECOMMEND_FAILED_INVALID_URI;
             } else {
-                ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
+                ret.recommendedInstallLocation = InstallLocationUtils.RECOMMEND_FAILED_INVALID_APK;
             }
 
             return ret;
         }
 
-        final int recommendedInstallLocation = PackageHelper.resolveInstallLocation(context,
-                pkg.getPackageName(), pkg.getInstallLocation(), sizeBytes, flags);
-
+        final PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(
+                PackageInstaller.SessionParams.MODE_INVALID);
+        sessionParams.appPackageName = pkg.getPackageName();
+        sessionParams.installLocation = pkg.getInstallLocation();
+        sessionParams.sizeBytes = sizeBytes;
+        sessionParams.installFlags = flags;
+        final int recommendedInstallLocation;
+        try {
+            recommendedInstallLocation = InstallLocationUtils.resolveInstallLocation(context,
+                    sessionParams);
+        } catch (IOException e) {
+            throw new IllegalStateException(e);
+        }
         ret.packageName = pkg.getPackageName();
         ret.splitNames = pkg.getSplitNames();
         ret.versionCode = pkg.getVersionCode();
@@ -837,7 +848,6 @@
         ret.recommendedInstallLocation = recommendedInstallLocation;
         ret.multiArch = pkg.isMultiArch();
         ret.debuggable = pkg.isDebuggable();
-
         return ret;
     }
 
@@ -857,7 +867,7 @@
                 throw new PackageManagerException(result.getErrorCode(),
                         result.getErrorMessage(), result.getException());
             }
-            return PackageHelper.calculateInstalledSize(result.getResult(), abiOverride);
+            return InstallLocationUtils.calculateInstalledSize(result.getResult(), abiOverride);
         } catch (PackageManagerException | IOException e) {
             Slog.w(TAG, "Failed to calculate installed size: " + e);
             return -1;
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 4b59206..d4fcd06 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -98,7 +98,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
@@ -593,7 +593,7 @@
                         null /* splitApkPaths */, null /* splitRevisionCodes */,
                         apkLite.getTargetSdkVersion(), null /* requiredSplitTypes */,
                         null /* splitTypes */);
-                sessionSize += PackageHelper.calculateInstalledSize(pkgLite,
+                sessionSize += InstallLocationUtils.calculateInstalledSize(pkgLite,
                         params.sessionParams.abiOverride, fd.getFileDescriptor());
             } catch (IOException e) {
                 getErrPrintWriter().println("Error: Failed to parse APK file: " + inPath);
@@ -1649,11 +1649,11 @@
     private int runGetInstallLocation() throws RemoteException {
         int loc = mInterface.getInstallLocation();
         String locStr = "invalid";
-        if (loc == PackageHelper.APP_INSTALL_AUTO) {
+        if (loc == InstallLocationUtils.APP_INSTALL_AUTO) {
             locStr = "auto";
-        } else if (loc == PackageHelper.APP_INSTALL_INTERNAL) {
+        } else if (loc == InstallLocationUtils.APP_INSTALL_INTERNAL) {
             locStr = "internal";
-        } else if (loc == PackageHelper.APP_INSTALL_EXTERNAL) {
+        } else if (loc == InstallLocationUtils.APP_INSTALL_EXTERNAL) {
             locStr = "external";
         }
         getOutPrintWriter().println(loc + "[" + locStr + "]");
diff --git a/services/core/java/com/android/server/pm/PackageSessionVerifier.java b/services/core/java/com/android/server/pm/PackageSessionVerifier.java
index 9bfb7d1..9db215e 100644
--- a/services/core/java/com/android/server/pm/PackageSessionVerifier.java
+++ b/services/core/java/com/android/server/pm/PackageSessionVerifier.java
@@ -28,7 +28,6 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.SigningDetails;
-import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
 import android.content.rollback.RollbackInfo;
@@ -43,11 +42,12 @@
 import android.util.apk.ApkSignatureVerifier;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
 import com.android.server.LocalServices;
 import com.android.server.SystemConfig;
 import com.android.server.pm.parsing.PackageParser2;
 import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils;
 import com.android.server.rollback.RollbackManagerInternal;
 
 import java.io.File;
@@ -291,8 +291,8 @@
             throws PackageManagerException {
         // Before marking the session as ready, start checkpoint service if available
         try {
-            if (PackageHelper.getStorageManager().supportsCheckpoint()) {
-                PackageHelper.getStorageManager().startCheckpoint(2);
+            if (InstallLocationUtils.getStorageManager().supportsCheckpoint()) {
+                InstallLocationUtils.getStorageManager().startCheckpoint(2);
             }
         } catch (Exception e) {
             // Failed to get hold of StorageManager
@@ -544,7 +544,7 @@
      */
     private void checkActiveSessions() throws PackageManagerException {
         try {
-            checkActiveSessions(PackageHelper.getStorageManager().supportsCheckpoint());
+            checkActiveSessions(InstallLocationUtils.getStorageManager().supportsCheckpoint());
         } catch (RemoteException e) {
             throw new PackageManagerException(SessionInfo.SESSION_VERIFICATION_FAILED,
                     "Can't query fs-checkpoint status : " + e);
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 29de555..f63f8f4 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -53,7 +53,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.Preconditions;
 import com.android.server.LocalServices;
@@ -237,7 +237,7 @@
                     mApexManager.revertActiveSessions();
                 }
 
-                PackageHelper.getStorageManager().abortChanges(
+                InstallLocationUtils.getStorageManager().abortChanges(
                         "abort-staged-install", false /*retry*/);
             }
         } catch (Exception e) {
@@ -674,8 +674,8 @@
         boolean needsCheckpoint = false;
         boolean supportsCheckpoint = false;
         try {
-            supportsCheckpoint = PackageHelper.getStorageManager().supportsCheckpoint();
-            needsCheckpoint = PackageHelper.getStorageManager().needsCheckpoint();
+            supportsCheckpoint = InstallLocationUtils.getStorageManager().supportsCheckpoint();
+            needsCheckpoint = InstallLocationUtils.getStorageManager().needsCheckpoint();
         } catch (RemoteException e) {
             // This means that vold has crashed, and device is in a bad state.
             throw new IllegalStateException("Failed to get checkpoint status", e);
diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java
index bb7e55a..1372098 100644
--- a/services/core/java/com/android/server/pm/StorageEventHelper.java
+++ b/services/core/java/com/android/server/pm/StorageEventHelper.java
@@ -32,7 +32,6 @@
 import android.content.pm.PackagePartitions;
 import android.content.pm.UserInfo;
 import android.content.pm.VersionedPackage;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.UserHandle;
@@ -48,6 +47,7 @@
 import com.android.internal.policy.AttributeCache;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -150,7 +150,7 @@
                 final AndroidPackage pkg;
                 try {
                     pkg = installPackageHelper.scanSystemPackageTracedLI(
-                            ps.getPath(), parseFlags, SCAN_INITIAL, 0, null);
+                            ps.getPath(), parseFlags, SCAN_INITIAL, null);
                     loaded.add(pkg);
 
                 } catch (PackageManagerException e) {
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 73ec2cd..77d6310 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -33,6 +33,7 @@
 import android.net.Uri;
 import android.os.BatteryStats;
 import android.os.Handler;
+import android.os.IWakeLockCallback;
 import android.os.Looper;
 import android.os.Message;
 import android.os.PowerManager;
@@ -215,14 +216,15 @@
      * Called when a wake lock is acquired.
      */
     public void onWakeLockAcquired(int flags, String tag, String packageName,
-            int ownerUid, int ownerPid, WorkSource workSource, String historyTag) {
+            int ownerUid, int ownerPid, WorkSource workSource, String historyTag,
+            IWakeLockCallback callback) {
         if (DEBUG) {
             Slog.d(TAG, "onWakeLockAcquired: flags=" + flags + ", tag=\"" + tag
                     + "\", packageName=" + packageName
                     + ", ownerUid=" + ownerUid + ", ownerPid=" + ownerPid
                     + ", workSource=" + workSource);
         }
-
+        notifyWakeLockListener(callback, true);
         final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
         if (monitorType >= 0) {
             try {
@@ -300,8 +302,9 @@
      */
     public void onWakeLockChanging(int flags, String tag, String packageName,
             int ownerUid, int ownerPid, WorkSource workSource, String historyTag,
-            int newFlags, String newTag, String newPackageName, int newOwnerUid,
-            int newOwnerPid, WorkSource newWorkSource, String newHistoryTag) {
+            IWakeLockCallback callback, int newFlags, String newTag, String newPackageName,
+            int newOwnerUid, int newOwnerPid, WorkSource newWorkSource, String newHistoryTag,
+            IWakeLockCallback newCallback) {
 
         final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
         final int newMonitorType = getBatteryStatsWakeLockMonitorType(newFlags);
@@ -323,10 +326,16 @@
             } catch (RemoteException ex) {
                 // Ignore
             }
-        } else {
-            onWakeLockReleased(flags, tag, packageName, ownerUid, ownerPid, workSource, historyTag);
+        } else if (!PowerManagerService.isSameCallback(callback, newCallback)) {
+            onWakeLockReleased(flags, tag, packageName, ownerUid, ownerPid, workSource, historyTag,
+                    null /* Do not notify the old callback */);
             onWakeLockAcquired(newFlags, newTag, newPackageName, newOwnerUid, newOwnerPid,
-                    newWorkSource, newHistoryTag);
+                    newWorkSource, newHistoryTag, newCallback /* notify the new callback */);
+        } else {
+            onWakeLockReleased(flags, tag, packageName, ownerUid, ownerPid, workSource, historyTag,
+                    callback);
+            onWakeLockAcquired(newFlags, newTag, newPackageName, newOwnerUid, newOwnerPid,
+                    newWorkSource, newHistoryTag, newCallback);
         }
     }
 
@@ -334,14 +343,15 @@
      * Called when a wake lock is released.
      */
     public void onWakeLockReleased(int flags, String tag, String packageName,
-            int ownerUid, int ownerPid, WorkSource workSource, String historyTag) {
+            int ownerUid, int ownerPid, WorkSource workSource, String historyTag,
+            IWakeLockCallback callback) {
         if (DEBUG) {
             Slog.d(TAG, "onWakeLockReleased: flags=" + flags + ", tag=\"" + tag
                     + "\", packageName=" + packageName
                     + ", ownerUid=" + ownerUid + ", ownerPid=" + ownerPid
                     + ", workSource=" + workSource);
         }
-
+        notifyWakeLockListener(callback, false);
         final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
         if (monitorType >= 0) {
             try {
@@ -859,6 +869,18 @@
         return enabled && dndOff;
     }
 
+    private void notifyWakeLockListener(IWakeLockCallback callback, boolean isEnabled) {
+        if (callback != null) {
+            mHandler.post(() -> {
+                try {
+                    callback.onStateChanged(isEnabled);
+                } catch (RemoteException e) {
+                    throw new IllegalArgumentException("Wakelock.mCallback is already dead.", e);
+                }
+            });
+        }
+    }
+
     private final class NotifierHandler extends Handler {
 
         public NotifierHandler(Looper looper) {
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index efcfbdd..3857072 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -66,6 +66,7 @@
 import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.IPowerManager;
+import android.os.IWakeLockCallback;
 import android.os.Looper;
 import android.os.Message;
 import android.os.ParcelDuration;
@@ -1436,7 +1437,8 @@
     }
 
     private void acquireWakeLockInternal(IBinder lock, int displayId, int flags, String tag,
-            String packageName, WorkSource ws, String historyTag, int uid, int pid) {
+            String packageName, WorkSource ws, String historyTag, int uid, int pid,
+            @Nullable IWakeLockCallback callback) {
         synchronized (mLock) {
             if (displayId != Display.INVALID_DISPLAY) {
                 final DisplayInfo displayInfo =
@@ -1460,11 +1462,12 @@
             boolean notifyAcquire;
             if (index >= 0) {
                 wakeLock = mWakeLocks.get(index);
-                if (!wakeLock.hasSameProperties(flags, tag, ws, uid, pid)) {
+                if (!wakeLock.hasSameProperties(flags, tag, ws, uid, pid, callback)) {
                     // Update existing wake lock.  This shouldn't happen but is harmless.
                     notifyWakeLockChangingLocked(wakeLock, flags, tag, packageName,
-                            uid, pid, ws, historyTag);
-                    wakeLock.updateProperties(flags, tag, packageName, ws, historyTag, uid, pid);
+                            uid, pid, ws, historyTag, callback);
+                    wakeLock.updateProperties(flags, tag, packageName, ws, historyTag, uid, pid,
+                            callback);
                 }
                 notifyAcquire = false;
             } else {
@@ -1476,12 +1479,7 @@
                 }
                 state.mNumWakeLocks++;
                 wakeLock = new WakeLock(lock, displayId, flags, tag, packageName, ws, historyTag,
-                        uid, pid, state);
-                try {
-                    lock.linkToDeath(wakeLock, 0);
-                } catch (RemoteException ex) {
-                    throw new IllegalArgumentException("Wake lock is already dead.");
-                }
+                        uid, pid, state, callback);
                 mWakeLocks.add(wakeLock);
                 setWakeLockDisabledStateLocked(wakeLock);
                 notifyAcquire = true;
@@ -1576,11 +1574,8 @@
                 mRequestWaitForNegativeProximity = true;
             }
 
-            try {
-                wakeLock.mLock.unlinkToDeath(wakeLock, 0);
-            } catch (NoSuchElementException e) {
-                Slog.wtf(TAG, "Failed to unlink wakelock", e);
-            }
+            wakeLock.unlinkToDeath();
+            wakeLock.setDisabled(true);
             removeWakeLockLocked(wakeLock, index);
         }
     }
@@ -1650,13 +1645,41 @@
             if (!wakeLock.hasSameWorkSource(ws)) {
                 notifyWakeLockChangingLocked(wakeLock, wakeLock.mFlags, wakeLock.mTag,
                         wakeLock.mPackageName, wakeLock.mOwnerUid, wakeLock.mOwnerPid,
-                        ws, historyTag);
+                        ws, historyTag, null);
                 wakeLock.mHistoryTag = historyTag;
                 wakeLock.updateWorkSource(ws);
             }
         }
     }
 
+    private void updateWakeLockCallbackInternal(IBinder lock, IWakeLockCallback callback,
+            int callingUid) {
+        synchronized (mLock) {
+            int index = findWakeLockIndexLocked(lock);
+            if (index < 0) {
+                if (DEBUG_SPEW) {
+                    Slog.d(TAG, "updateWakeLockCallbackInternal: lock=" + Objects.hashCode(lock)
+                            + " [not found]");
+                }
+                throw new IllegalArgumentException("Wake lock not active: " + lock
+                        + " from uid " + callingUid);
+            }
+
+            WakeLock wakeLock = mWakeLocks.get(index);
+            if (DEBUG_SPEW) {
+                Slog.d(TAG, "updateWakeLockCallbackInternal: lock=" + Objects.hashCode(lock)
+                        + " [" + wakeLock.mTag + "]");
+            }
+
+            if (!isSameCallback(callback, wakeLock.mCallback)) {
+                notifyWakeLockChangingLocked(wakeLock, wakeLock.mFlags, wakeLock.mTag,
+                        wakeLock.mPackageName, wakeLock.mOwnerUid, wakeLock.mOwnerPid,
+                        wakeLock.mWorkSource, wakeLock.mHistoryTag, callback);
+                wakeLock.mCallback = callback;
+            }
+        }
+    }
+
     @GuardedBy("mLock")
     private int findWakeLockIndexLocked(IBinder lock) {
         final int count = mWakeLocks.size();
@@ -1684,7 +1707,7 @@
             wakeLock.mNotifiedAcquired = true;
             mNotifier.onWakeLockAcquired(wakeLock.mFlags, wakeLock.mTag, wakeLock.mPackageName,
                     wakeLock.mOwnerUid, wakeLock.mOwnerPid, wakeLock.mWorkSource,
-                    wakeLock.mHistoryTag);
+                    wakeLock.mHistoryTag, wakeLock.mCallback);
             restartNofifyLongTimerLocked(wakeLock);
         }
     }
@@ -1726,11 +1749,13 @@
 
     @GuardedBy("mLock")
     private void notifyWakeLockChangingLocked(WakeLock wakeLock, int flags, String tag,
-            String packageName, int uid, int pid, WorkSource ws, String historyTag) {
+            String packageName, int uid, int pid, WorkSource ws, String historyTag,
+            IWakeLockCallback callback) {
         if (mSystemReady && wakeLock.mNotifiedAcquired) {
             mNotifier.onWakeLockChanging(wakeLock.mFlags, wakeLock.mTag, wakeLock.mPackageName,
                     wakeLock.mOwnerUid, wakeLock.mOwnerPid, wakeLock.mWorkSource,
-                    wakeLock.mHistoryTag, flags, tag, packageName, uid, pid, ws, historyTag);
+                    wakeLock.mHistoryTag, wakeLock.mCallback, flags, tag, packageName, uid, pid, ws,
+                    historyTag, callback);
             notifyWakeLockLongFinishedLocked(wakeLock);
             // Changing the wake lock will count as releasing the old wake lock(s) and
             // acquiring the new ones...  we do this because otherwise once a wakelock
@@ -1747,7 +1772,7 @@
             wakeLock.mAcquireTime = 0;
             mNotifier.onWakeLockReleased(wakeLock.mFlags, wakeLock.mTag,
                     wakeLock.mPackageName, wakeLock.mOwnerUid, wakeLock.mOwnerPid,
-                    wakeLock.mWorkSource, wakeLock.mHistoryTag);
+                    wakeLock.mWorkSource, wakeLock.mHistoryTag, wakeLock.mCallback);
             notifyWakeLockLongFinishedLocked(wakeLock);
         }
     }
@@ -4045,10 +4070,7 @@
                     }
                 }
             }
-            if (wakeLock.mDisabled != disabled) {
-                wakeLock.mDisabled = disabled;
-                return true;
-            }
+            return wakeLock.setDisabled(disabled);
         }
         return false;
     }
@@ -5041,10 +5063,11 @@
         public boolean mNotifiedAcquired;
         public boolean mNotifiedLong;
         public boolean mDisabled;
+        public IWakeLockCallback mCallback;
 
         public WakeLock(IBinder lock, int displayId, int flags, String tag, String packageName,
                 WorkSource workSource, String historyTag, int ownerUid, int ownerPid,
-                UidState uidState) {
+                UidState uidState, @Nullable IWakeLockCallback callback) {
             mLock = lock;
             mDisplayId = displayId;
             mFlags = flags;
@@ -5055,15 +5078,43 @@
             mOwnerUid = ownerUid;
             mOwnerPid = ownerPid;
             mUidState = uidState;
+            mCallback = callback;
+            linkToDeath();
         }
 
         @Override
         public void binderDied() {
+            unlinkToDeath();
             PowerManagerService.this.handleWakeLockDeath(this);
         }
 
+        private void linkToDeath() {
+            try {
+                mLock.linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                throw new IllegalArgumentException("Wakelock.mLock is already dead.");
+            }
+        }
+
+        @GuardedBy("mLock")
+        void unlinkToDeath() {
+            try {
+                mLock.unlinkToDeath(this, 0);
+            } catch (NoSuchElementException e) {
+                Slog.wtf(TAG, "Failed to unlink Wakelock.mLock", e);
+            }
+        }
+
+        public boolean setDisabled(boolean disabled) {
+            if (mDisabled != disabled) {
+                mDisabled = disabled;
+                return true;
+            } else {
+                return false;
+            }
+        }
         public boolean hasSameProperties(int flags, String tag, WorkSource workSource,
-                int ownerUid, int ownerPid) {
+                int ownerUid, int ownerPid, IWakeLockCallback callback) {
             return mFlags == flags
                     && mTag.equals(tag)
                     && hasSameWorkSource(workSource)
@@ -5072,7 +5123,8 @@
         }
 
         public void updateProperties(int flags, String tag, String packageName,
-                WorkSource workSource, String historyTag, int ownerUid, int ownerPid) {
+                WorkSource workSource, String historyTag, int ownerUid, int ownerPid,
+                IWakeLockCallback callback) {
             if (!mPackageName.equals(packageName)) {
                 throw new IllegalStateException("Existing wake lock package name changed: "
                         + mPackageName + " to " + packageName);
@@ -5089,6 +5141,7 @@
             mTag = tag;
             updateWorkSource(workSource);
             mHistoryTag = historyTag;
+            mCallback = callback;
         }
 
         public boolean hasSameWorkSource(WorkSource workSource) {
@@ -5307,11 +5360,12 @@
 
         @Override // Binder call
         public void acquireWakeLockWithUid(IBinder lock, int flags, String tag,
-                String packageName, int uid, int displayId) {
+                String packageName, int uid, int displayId, IWakeLockCallback callback) {
             if (uid < 0) {
                 uid = Binder.getCallingUid();
             }
-            acquireWakeLock(lock, flags, tag, packageName, new WorkSource(uid), null, displayId);
+            acquireWakeLock(lock, flags, tag, packageName, new WorkSource(uid), null,
+                    displayId, callback);
         }
 
         @Override // Binder call
@@ -5346,7 +5400,8 @@
 
         @Override // Binder call
         public void acquireWakeLock(IBinder lock, int flags, String tag, String packageName,
-                WorkSource ws, String historyTag, int displayId) {
+                WorkSource ws, String historyTag, int displayId,
+                @Nullable IWakeLockCallback callback) {
             if (lock == null) {
                 throw new IllegalArgumentException("lock must not be null");
             }
@@ -5386,7 +5441,7 @@
             final long ident = Binder.clearCallingIdentity();
             try {
                 acquireWakeLockInternal(lock, displayId, flags, tag, packageName, ws, historyTag,
-                        uid, pid);
+                        uid, pid, callback);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -5395,7 +5450,8 @@
         @Override // Binder call
         public void acquireWakeLockAsync(IBinder lock, int flags, String tag, String packageName,
                 WorkSource ws, String historyTag) {
-            acquireWakeLock(lock, flags, tag, packageName, ws, historyTag, Display.INVALID_DISPLAY);
+            acquireWakeLock(lock, flags, tag, packageName, ws, historyTag, Display.INVALID_DISPLAY,
+                    null);
         }
 
         @Override // Binder call
@@ -5463,6 +5519,23 @@
         }
 
         @Override // Binder call
+        public void updateWakeLockCallback(IBinder lock, IWakeLockCallback callback) {
+            if (lock == null) {
+                throw new IllegalArgumentException("lock must not be null");
+            }
+
+            mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
+
+            final int callingUid = Binder.getCallingUid();
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                updateWakeLockCallbackInternal(lock, callback, callingUid);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        @Override // Binder call
         public boolean isWakeLockLevelSupported(int level) {
             final long ident = Binder.clearCallingIdentity();
             try {
@@ -6510,4 +6583,15 @@
         }
     };
 
+    static boolean isSameCallback(IWakeLockCallback callback1,
+            IWakeLockCallback callback2) {
+        if (callback1 == callback2) {
+            return true;
+        }
+        if (callback1 != null && callback2 != null
+                && callback1.asBinder() == callback2.asBinder()) {
+            return true;
+        }
+        return false;
+    }
 }
diff --git a/services/core/java/com/android/server/resources/ResourcesManagerService.java b/services/core/java/com/android/server/resources/ResourcesManagerService.java
new file mode 100644
index 0000000..cc27546
--- /dev/null
+++ b/services/core/java/com/android/server/resources/ResourcesManagerService.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.resources;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.IResourcesManager;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+
+import com.android.server.SystemService;
+import com.android.server.am.ActivityManagerService;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * A service for managing information about ResourcesManagers
+ */
+public class ResourcesManagerService extends SystemService {
+    private ActivityManagerService mActivityManagerService;
+
+    /**
+     * Initializes the system service.
+     * <p>
+     * Subclasses must define a single argument constructor that accepts the context
+     * and passes it to super.
+     * </p>
+     *
+     * @param context The system server context.
+     */
+    public ResourcesManagerService(@NonNull Context context) {
+        super(context);
+        publishBinderService(Context.RESOURCES_SERVICE, mService);
+    }
+
+    @Override
+    public void onStart() {
+        // Intentionally left empty.
+    }
+
+    private final IBinder mService = new IResourcesManager.Stub() {
+        @Override
+        public boolean dumpResources(String process, ParcelFileDescriptor fd,
+                RemoteCallback callback) throws RemoteException {
+            final int callingUid = Binder.getCallingUid();
+            if (callingUid != Process.ROOT_UID && callingUid != Process.SHELL_UID) {
+                callback.sendResult(null);
+                throw new SecurityException("dump should only be called by shell");
+            }
+            return mActivityManagerService.dumpResources(process, fd, callback);
+        }
+
+        @Override
+        protected void dump(@NonNull FileDescriptor fd,
+                @NonNull PrintWriter pw, @Nullable String[] args) {
+            try {
+                mActivityManagerService.dumpAllResources(ParcelFileDescriptor.dup(fd), pw);
+            } catch (Exception e) {
+                pw.println("Exception while trying to dump all resources: " + e.getMessage());
+                e.printStackTrace(pw);
+            }
+        }
+
+        @Override
+        public int handleShellCommand(@NonNull ParcelFileDescriptor in,
+                @NonNull ParcelFileDescriptor out,
+                @NonNull ParcelFileDescriptor err,
+                @NonNull String[] args) {
+            return (new ResourcesManagerShellCommand(this)).exec(
+                    this,
+                    in.getFileDescriptor(),
+                    out.getFileDescriptor(),
+                    err.getFileDescriptor(),
+                    args);
+        }
+    };
+
+    public void setActivityManagerService(
+            ActivityManagerService activityManagerService) {
+        mActivityManagerService = activityManagerService;
+    }
+}
diff --git a/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java b/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java
new file mode 100644
index 0000000..7d8336a
--- /dev/null
+++ b/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.resources;
+
+import android.content.res.IResourcesManager;
+import android.os.ConditionVariable;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.ShellCommand;
+import android.util.Slog;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+/**
+ * Shell command handler for resources related commands
+ */
+public class ResourcesManagerShellCommand extends ShellCommand {
+    private static final String TAG = "ResourcesManagerShellCommand";
+
+    private final IResourcesManager mInterface;
+
+    public ResourcesManagerShellCommand(IResourcesManager anInterface) {
+        mInterface = anInterface;
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        if (cmd == null) {
+            return handleDefaultCommands(cmd);
+        }
+        final PrintWriter err = getErrPrintWriter();
+        try {
+            switch (cmd) {
+                case "dump":
+                    return dumpResources();
+                default:
+                    return handleDefaultCommands(cmd);
+            }
+        } catch (IllegalArgumentException e) {
+            err.println("Error: " + e.getMessage());
+        } catch (RemoteException e) {
+            err.println("Remote exception: " + e);
+        }
+        return -1;
+    }
+
+    private int dumpResources() throws RemoteException {
+        String processId = getNextArgRequired();
+        try {
+            ConditionVariable lock = new ConditionVariable();
+            RemoteCallback
+                    finishCallback = new RemoteCallback(result -> lock.open(), null);
+
+            if (!mInterface.dumpResources(processId,
+                    ParcelFileDescriptor.dup(getOutFileDescriptor()), finishCallback)) {
+                getErrPrintWriter().println("RESOURCES DUMP FAILED on process " + processId);
+                return -1;
+            }
+            lock.block(5000);
+            return 0;
+        } catch (IOException e) {
+            Slog.e(TAG, "Exception while dumping resources", e);
+            getErrPrintWriter().println("Exception while dumping resources: " + e.getMessage());
+        }
+        return -1;
+    }
+
+    @Override
+    public void onHelp() {
+        final PrintWriter out = getOutPrintWriter();
+        out.println("Resources manager commands:");
+        out.println("  help");
+        out.println("    Print this help text.");
+        out.println("  dump <PROCESS>");
+        out.println("    Dump the Resources objects in use as well as the history of Resources");
+
+    }
+}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 344edbb..8a87c96 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -158,6 +158,8 @@
     private final SparseArray<UiState> mDisplayUiState = new SparseArray<>();
     @GuardedBy("mLock")
     private IUdfpsHbmListener mUdfpsHbmListener;
+    @GuardedBy("mLock")
+    private IBiometricContextListener mBiometricContextListener;
 
     @GuardedBy("mCurrentRequestAddTilePackages")
     private final ArrayMap<String, Long> mCurrentRequestAddTilePackages = new ArrayMap<>();
@@ -897,6 +899,9 @@
     @Override
     public void setBiometicContextListener(IBiometricContextListener listener) {
         enforceStatusBarService();
+        synchronized (mLock) {
+            mBiometricContextListener = listener;
+        }
         if (mBar != null) {
             try {
                 mBar.setBiometicContextListener(listener);
@@ -1327,6 +1332,7 @@
         mHandler.post(() -> {
             synchronized (mLock) {
                 setUdfpsHbmListener(mUdfpsHbmListener);
+                setBiometicContextListener(mBiometricContextListener);
             }
         });
     }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index f8d7b78..5f04b7e 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -46,6 +46,9 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.activityTypeToString;
 import static android.app.WindowConfiguration.isSplitScreenWindowingMode;
+import static android.app.admin.DevicePolicyResources.Drawables.Source.PROFILE_SWITCH_ANIMATION;
+import static android.app.admin.DevicePolicyResources.Drawables.Style.OUTLINE;
+import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
 import static android.content.Intent.ACTION_MAIN;
 import static android.content.Intent.CATEGORY_HOME;
 import static android.content.Intent.CATEGORY_LAUNCHER;
@@ -241,6 +244,7 @@
 import android.app.TaskInfo.CameraCompatControlState;
 import android.app.WaitResult;
 import android.app.WindowConfiguration;
+import android.app.admin.DevicePolicyManager;
 import android.app.servertransaction.ActivityConfigurationChangeItem;
 import android.app.servertransaction.ActivityLifecycleItem;
 import android.app.servertransaction.ActivityRelaunchItem;
@@ -272,6 +276,7 @@
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.hardware.HardwareBuffer;
 import android.net.Uri;
 import android.os.Binder;
@@ -538,6 +543,15 @@
     // Tracking splash screen status from previous activity
     boolean mSplashScreenStyleEmpty = false;
 
+    Drawable mEnterpriseThumbnailDrawable;
+
+    private void updateEnterpriseThumbnailDrawable(Context context) {
+        DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
+        mEnterpriseThumbnailDrawable = dpm.getDrawable(
+                WORK_PROFILE_ICON, OUTLINE, PROFILE_SWITCH_ANIMATION,
+                () -> context.getDrawable(R.drawable.ic_corp_badge));
+    }
+
     static final int LAUNCH_SOURCE_TYPE_SYSTEM = 1;
     static final int LAUNCH_SOURCE_TYPE_HOME = 2;
     static final int LAUNCH_SOURCE_TYPE_SYSTEMUI = 3;
@@ -716,6 +730,11 @@
     @Nullable
     private Rect mLetterboxBoundsForFixedOrientationAndAspectRatio;
 
+    // Whether the activity is eligible to be letterboxed for fixed orientation with respect to its
+    // requested orientation, even when it's letterbox for another reason (e.g., size compat mode)
+    // and therefore #isLetterboxedForFixedOrientationAndAspectRatio returns false.
+    private boolean mIsEligibleForFixedOrientationLetterbox;
+
     // State of the Camera app compat control which is used to correct stretched viewfinder
     // in apps that don't handle all possible configurations and changes between them correctly.
     @CameraCompatControlState
@@ -1925,6 +1944,8 @@
         mAtmService.mPackageConfigPersister.updateConfigIfNeeded(this, mUserId, packageName);
 
         mActivityRecordInputSink = new ActivityRecordInputSink(this);
+
+        updateEnterpriseThumbnailDrawable(mAtmService.mUiContext);
     }
 
     /**
@@ -6988,12 +7009,11 @@
             return;
         }
         final Rect frame = win.getRelativeFrame();
-        final int thumbnailDrawableRes = task.mUserId == mWmService.mCurrentUserId
-                ? R.drawable.ic_account_circle
-                : R.drawable.ic_corp_badge;
-        final HardwareBuffer thumbnail =
-                getDisplayContent().mAppTransition
-                        .createCrossProfileAppsThumbnail(thumbnailDrawableRes, frame);
+        final Drawable thumbnailDrawable = task.mUserId == mWmService.mCurrentUserId
+                ? mAtmService.mUiContext.getDrawable(R.drawable.ic_account_circle)
+                : mEnterpriseThumbnailDrawable;
+        final HardwareBuffer thumbnail = getDisplayContent().mAppTransition
+                .createCrossProfileAppsThumbnail(thumbnailDrawable, frame);
         if (thumbnail == null) {
             return;
         }
@@ -7627,6 +7647,24 @@
     }
 
     /**
+     * Whether this activity is eligible for letterbox eduction.
+     *
+     * <p>Conditions that need to be met:
+     *
+     * <ul>
+     *     <li>{@link LetterboxConfiguration#getIsEducationEnabled} is true.
+     *     <li>The activity is eligible for fixed orientation letterbox.
+     *     <li>The activity is in fullscreen.
+     * </ul>
+     */
+    // TODO(b/215316431): Add tests
+    boolean isEligibleForLetterboxEducation() {
+        return mWmService.mLetterboxConfiguration.getIsEducationEnabled()
+                && mIsEligibleForFixedOrientationLetterbox
+                && getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
+    }
+
+    /**
      * In some cases, applying insets to bounds changes the orientation. For example, if a
      * close-to-square display rotates to portrait to respect a portrait orientation activity, after
      * insets such as the status and nav bars are applied, the activity may actually have a
@@ -7688,6 +7726,7 @@
     private void resolveFixedOrientationConfiguration(@NonNull Configuration newParentConfig,
             int windowingMode) {
         mLetterboxBoundsForFixedOrientationAndAspectRatio = null;
+        mIsEligibleForFixedOrientationLetterbox = false;
         final Rect parentBounds = newParentConfig.windowConfiguration.getBounds();
         final Rect stableBounds = new Rect();
         // If orientation is respected when insets are applied, then stableBounds will be empty.
@@ -7727,8 +7766,11 @@
         // make it fit the available bounds by scaling down its bounds.
         final int forcedOrientation = getRequestedConfigurationOrientation();
 
-        if (forcedOrientation == ORIENTATION_UNDEFINED
-                || (forcedOrientation == parentOrientation && orientationRespectedWithInsets)) {
+        mIsEligibleForFixedOrientationLetterbox = forcedOrientation != ORIENTATION_UNDEFINED
+                && forcedOrientation != parentOrientation;
+
+        if (!mIsEligibleForFixedOrientationLetterbox && (forcedOrientation == ORIENTATION_UNDEFINED
+                || orientationRespectedWithInsets)) {
             return;
         }
 
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index d5abe4f..56adcfd 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -93,7 +93,6 @@
 import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_AFTER_ANIM;
 import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_NONE;
 
-import android.annotation.DrawableRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ComponentName;
@@ -101,6 +100,7 @@
 import android.content.res.Configuration;
 import android.content.res.TypedArray;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.hardware.HardwareBuffer;
 import android.os.Binder;
 import android.os.Debug;
@@ -539,8 +539,8 @@
      * animation.
      */
     HardwareBuffer createCrossProfileAppsThumbnail(
-            @DrawableRes int thumbnailDrawableRes, Rect frame) {
-        return mTransitionAnimation.createCrossProfileAppsThumbnail(thumbnailDrawableRes, frame);
+            Drawable thumbnailDrawable, Rect frame) {
+        return mTransitionAnimation.createCrossProfileAppsThumbnail(thumbnailDrawable, frame);
     }
 
     Animation createCrossProfileAppsThumbnailAnimationLocked(Rect appRect) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 4009220..c87027d 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -5486,26 +5486,46 @@
     }
 
     void updateKeepClearAreas() {
+        final List<Rect> restrictedKeepClearAreas = new ArrayList();
+        final List<Rect> unrestrictedKeepClearAreas = new ArrayList();
+        getKeepClearAreas(restrictedKeepClearAreas, unrestrictedKeepClearAreas);
         mWmService.mDisplayNotificationController.dispatchKeepClearAreasChanged(
-                this, getKeepClearAreas());
+                this, restrictedKeepClearAreas, unrestrictedKeepClearAreas);
     }
 
     /**
-     * Returns all keep-clear areas from visible windows on this display.
+     * Fills {@param outRestricted} with all keep-clear areas from visible, relevant windows
+     * on this display, which set restricted keep-clear areas.
+     * Fills {@param outUnrestricted} with keep-clear areas from visible, relevant windows on this
+     * display, which set unrestricted keep-clear areas.
+     *
+     * For context on restricted vs unrestricted keep-clear areas, see
+     * {@link android.Manifest.permission.USE_UNRESTRICTED_KEEP_CLEAR_AREAS}.
      */
-    ArrayList<Rect> getKeepClearAreas() {
-        final ArrayList<Rect> keepClearAreas = new ArrayList<Rect>();
+    void getKeepClearAreas(List<Rect> outRestricted, List<Rect> outUnrestricted) {
         final Matrix tmpMatrix = new Matrix();
         final float[] tmpFloat9 = new float[9];
         forAllWindows(w -> {
             if (w.isVisible() && !w.inPinnedWindowingMode()) {
-                keepClearAreas.addAll(w.getKeepClearAreas(tmpMatrix, tmpFloat9));
+                if (w.mSession.mSetsUnrestrictedKeepClearAreas) {
+                    outUnrestricted.addAll(w.getKeepClearAreas(tmpMatrix, tmpFloat9));
+                } else {
+                    outRestricted.addAll(w.getKeepClearAreas(tmpMatrix, tmpFloat9));
+                }
             }
 
             // We stop traversing when we reach the base of a fullscreen app.
             return w.getWindowType() == TYPE_BASE_APPLICATION
                     && w.getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
         }, true);
+    }
+
+    /**
+     * Returns all keep-clear areas from visible, relevant windows on this display.
+     */
+    ArrayList<Rect> getKeepClearAreas() {
+        final ArrayList<Rect> keepClearAreas = new ArrayList<Rect>();
+        getKeepClearAreas(keepClearAreas, keepClearAreas);
         return keepClearAreas;
     }
 
diff --git a/services/core/java/com/android/server/wm/DisplayWindowListenerController.java b/services/core/java/com/android/server/wm/DisplayWindowListenerController.java
index 276dbe9..e18d539 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowListenerController.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowListenerController.java
@@ -120,12 +120,13 @@
         mDisplayListeners.finishBroadcast();
     }
 
-    void dispatchKeepClearAreasChanged(DisplayContent display, List<Rect> keepClearAreas) {
+    void dispatchKeepClearAreasChanged(DisplayContent display, List<Rect> restricted,
+            List<Rect> unrestricted) {
         int count = mDisplayListeners.beginBroadcast();
         for (int i = 0; i < count; ++i) {
             try {
                 mDisplayListeners.getBroadcastItem(i).onKeepClearAreasChanged(
-                        display.mDisplayId, keepClearAreas);
+                        display.mDisplayId, restricted, unrestricted);
             } catch (RemoteException e) {
             }
         }
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 1955e30..ad2767c 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -126,6 +126,9 @@
     @LetterboxReachabilityPosition
     private volatile int mLetterboxPositionForReachability;
 
+    // Whether education is allowed for letterboxed fullscreen apps.
+    private boolean mIsEducationEnabled;
+
     LetterboxConfiguration(Context systemUiContext) {
         mContext = systemUiContext;
         mFixedOrientationLetterboxAspectRatio = mContext.getResources().getFloat(
@@ -143,6 +146,8 @@
                 R.bool.config_letterboxIsReachabilityEnabled);
         mDefaultPositionForReachability = readLetterboxReachabilityPositionFromConfig(mContext);
         mLetterboxPositionForReachability = mDefaultPositionForReachability;
+        mIsEducationEnabled = mContext.getResources().getBoolean(
+                R.bool.config_letterboxIsEducationEnabled);
     }
 
     /**
@@ -501,4 +506,27 @@
         mLetterboxPositionForReachability = Math.max(mLetterboxPositionForReachability - 1, 0);
     }
 
+    /**
+     * Whether education is allowed for letterboxed fullscreen apps.
+     */
+    boolean getIsEducationEnabled() {
+        return mIsEducationEnabled;
+    }
+
+    /**
+     * Overrides whether education is allowed for letterboxed fullscreen apps.
+     */
+    void setIsEducationEnabled(boolean enabled) {
+        mIsEducationEnabled = enabled;
+    }
+
+    /**
+     * Resets whether education is allowed for letterboxed fullscreen apps to
+     * {@link R.bool.config_letterboxIsEducationEnabled}.
+     */
+    void resetIsEducationEnabled() {
+        mIsEducationEnabled = mContext.getResources().getBoolean(
+                R.bool.config_letterboxIsEducationEnabled);
+    }
+
 }
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 98acc46..2ae2b43 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 import static android.Manifest.permission.HIDE_OVERLAY_WINDOWS;
 import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
+import static android.Manifest.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS;
 import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
 import static android.Manifest.permission.SYSTEM_APPLICATION_OVERLAY;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
@@ -114,6 +115,7 @@
     private String mRelayoutTag;
     private final InsetsVisibilities mDummyRequestedVisibilities = new InsetsVisibilities();
     private final InsetsSourceControl[] mDummyControls =  new InsetsSourceControl[0];
+    final boolean mSetsUnrestrictedKeepClearAreas;
 
     public Session(WindowManagerService service, IWindowSessionCallback callback) {
         mService = service;
@@ -132,6 +134,9 @@
                         == PERMISSION_GRANTED;
         mCanStartTasksFromRecents = service.mContext.checkCallingOrSelfPermission(
                 START_TASKS_FROM_RECENTS) == PERMISSION_GRANTED;
+        mSetsUnrestrictedKeepClearAreas =
+                service.mContext.checkCallingOrSelfPermission(SET_UNRESTRICTED_KEEP_CLEAR_AREAS)
+                        == PERMISSION_GRANTED;
         mShowingAlertWindowNotificationAllowed = mService.mShowAlertWindowNotifications;
         mDragDropController = mService.mDragDropController;
         StringBuilder sb = new StringBuilder();
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 7d06526..97735a2 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3434,6 +3434,9 @@
         // Whether the direct top activity is in size compat mode on foreground.
         info.topActivityInSizeCompat = isTopActivityResumed
                 && mReuseActivitiesReport.top.inSizeCompatMode();
+        // Whether the direct top activity is eligible for letterbox education.
+        info.topActivityEligibleForLetterboxEducation = isTopActivityResumed
+                && mReuseActivitiesReport.top.isEligibleForLetterboxEducation();
         // Whether the direct top activity requested showing camera compat control.
         info.cameraCompatControlState = isTopActivityResumed
                 ? mReuseActivitiesReport.top.getCameraCompatControlState()
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 0f8587c..1cf4c1b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -822,6 +822,29 @@
         return 0;
     }
 
+    private int runSetLetterboxIsEducationEnabled(PrintWriter pw) throws RemoteException {
+        String arg = getNextArg();
+        final boolean enabled;
+        switch (arg) {
+            case "true":
+            case "1":
+                enabled = true;
+                break;
+            case "false":
+            case "0":
+                enabled = false;
+                break;
+            default:
+                getErrPrintWriter().println("Error: expected true, 1, false, 0, but got " + arg);
+                return -1;
+        }
+
+        synchronized (mInternal.mGlobalLock) {
+            mLetterboxConfiguration.setIsEducationEnabled(enabled);
+        }
+        return 0;
+    }
+
     private int runSetLetterboxStyle(PrintWriter pw) throws RemoteException {
         if (peekNextArg() == null) {
             getErrPrintWriter().println("Error: No arguments provided.");
@@ -859,6 +882,9 @@
                 case "--defaultPositionForReachability":
                     runSetLetterboxDefaultPositionForReachability(pw);
                     break;
+                case "--isEducationEnabled":
+                    runSetLetterboxIsEducationEnabled(pw);
+                    break;
                 default:
                     getErrPrintWriter().println(
                             "Error: Unrecognized letterbox style option: " + arg);
@@ -903,6 +929,9 @@
                     case "defaultPositionForReachability":
                         mLetterboxConfiguration.getDefaultPositionForReachability();
                         break;
+                    case "isEducationEnabled":
+                        mLetterboxConfiguration.getIsEducationEnabled();
+                        break;
                     default:
                         getErrPrintWriter().println(
                                 "Error: Unrecognized letterbox style option: " + arg);
@@ -998,6 +1027,7 @@
             mLetterboxConfiguration.resetLetterboxHorizontalPositionMultiplier();
             mLetterboxConfiguration.resetIsReachabilityEnabled();
             mLetterboxConfiguration.resetDefaultPositionForReachability();
+            mLetterboxConfiguration.resetIsEducationEnabled();
         }
     }
 
@@ -1014,6 +1044,8 @@
             pw.println("Default position for reachability: "
                     + LetterboxConfiguration.letterboxReachabilityPositionToString(
                             mLetterboxConfiguration.getDefaultPositionForReachability()));
+            pw.println("Is education enabled: "
+                    + mLetterboxConfiguration.getIsEducationEnabled());
 
             pw.println("Background type: "
                     + LetterboxConfiguration.letterboxBackgroundTypeToString(
@@ -1154,10 +1186,12 @@
         pw.println("      --defaultPositionForReachability [left|center|right]");
         pw.println("        Default horizontal position of app window  when reachability is.");
         pw.println("        enabled.");
+        pw.println("      --isEducationEnabled [true|1|false|0]");
+        pw.println("        Whether education is allowed for letterboxed fullscreen apps.");
         pw.println("  reset-letterbox-style [aspectRatio|cornerRadius|backgroundType");
         pw.println("      |backgroundColor|wallpaperBlurRadius|wallpaperDarkScrimAlpha");
         pw.println("      |horizontalPositionMultiplier|isReachabilityEnabled");
-        pw.println("      |defaultPositionMultiplierForReachability]");
+        pw.println("      isEducationEnabled||defaultPositionMultiplierForReachability]");
         pw.println("    Resets overrides to default values for specified properties separated");
         pw.println("    by space, e.g. 'reset-letterbox-style aspectRatio cornerRadius'.");
         pw.println("    If no arguments provided, all values will be reset.");
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 95ef5f7..99abf44 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -103,6 +103,7 @@
         "libappfuse",
         "libbinder_ndk",
         "libbinder",
+        "libchrome",
         "libcutils",
         "libcrypto",
         "liblog",
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index df5fb28..8cb27e1 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -457,6 +457,9 @@
     mInputManager->getReader().dump(dump);
     dump += "\n";
 
+    mInputManager->getUnwantedInteractionBlocker().dump(dump);
+    dump += "\n";
+
     mInputManager->getClassifier().dump(dump);
     dump += "\n";
 
@@ -704,6 +707,7 @@
 
 void NativeInputManager::notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) {
     ATRACE_CALL();
+    mInputManager->getUnwantedInteractionBlocker().notifyInputDevicesChanged(inputDevices);
     JNIEnv* env = jniEnv();
 
     size_t count = inputDevices.size();
diff --git a/services/core/jni/gnss/GnssAntennaInfoCallback.cpp b/services/core/jni/gnss/GnssAntennaInfoCallback.cpp
index fbc000b..99d06eb 100644
--- a/services/core/jni/gnss/GnssAntennaInfoCallback.cpp
+++ b/services/core/jni/gnss/GnssAntennaInfoCallback.cpp
@@ -226,17 +226,18 @@
             env->NewObject(class_gnssAntennaInfoBuilder, method_gnssAntennaInfoBuilderCtor);
 
     // Set fields
-    env->CallObjectMethod(gnssAntennaInfoBuilderObject,
-                          method_gnssAntennaInfoBuilderSetCarrierFrequencyMHz,
-                          gnssAntennaInfo.carrierFrequencyMHz);
-    env->CallObjectMethod(gnssAntennaInfoBuilderObject,
-                          method_gnssAntennaInfoBuilderSetPhaseCenterOffset, phaseCenterOffset);
-    env->CallObjectMethod(gnssAntennaInfoBuilderObject,
-                          method_gnssAntennaInfoBuilderSetPhaseCenterVariationCorrections,
-                          phaseCenterVariationCorrections);
-    env->CallObjectMethod(gnssAntennaInfoBuilderObject,
-                          method_gnssAntennaInfoBuilderSetSignalGainCorrections,
-                          signalGainCorrections);
+    callObjectMethodIgnoringResult(env, gnssAntennaInfoBuilderObject,
+                                   method_gnssAntennaInfoBuilderSetCarrierFrequencyMHz,
+                                   gnssAntennaInfo.carrierFrequencyMHz);
+    callObjectMethodIgnoringResult(env, gnssAntennaInfoBuilderObject,
+                                   method_gnssAntennaInfoBuilderSetPhaseCenterOffset,
+                                   phaseCenterOffset);
+    callObjectMethodIgnoringResult(env, gnssAntennaInfoBuilderObject,
+                                   method_gnssAntennaInfoBuilderSetPhaseCenterVariationCorrections,
+                                   phaseCenterVariationCorrections);
+    callObjectMethodIgnoringResult(env, gnssAntennaInfoBuilderObject,
+                                   method_gnssAntennaInfoBuilderSetSignalGainCorrections,
+                                   signalGainCorrections);
 
     // build
     jobject gnssAntennaInfoObject =
diff --git a/services/core/jni/gnss/GnssMeasurementCallback.cpp b/services/core/jni/gnss/GnssMeasurementCallback.cpp
index 6c0d5d9..34ca559 100644
--- a/services/core/jni/gnss/GnssMeasurementCallback.cpp
+++ b/services/core/jni/gnss/GnssMeasurementCallback.cpp
@@ -212,13 +212,14 @@
     jobject gnssMeasurementsEventBuilderObject =
             env->NewObject(class_gnssMeasurementsEventBuilder,
                            method_gnssMeasurementsEventBuilderCtor);
-    env->CallObjectMethod(gnssMeasurementsEventBuilderObject,
-                          method_gnssMeasurementsEventBuilderSetClock, clock);
-    env->CallObjectMethod(gnssMeasurementsEventBuilderObject,
-                          method_gnssMeasurementsEventBuilderSetMeasurements, measurementArray);
-    env->CallObjectMethod(gnssMeasurementsEventBuilderObject,
-                          method_gnssMeasurementsEventBuilderSetGnssAutomaticGainControls,
-                          gnssAgcArray);
+    callObjectMethodIgnoringResult(env, gnssMeasurementsEventBuilderObject,
+                                   method_gnssMeasurementsEventBuilderSetClock, clock);
+    callObjectMethodIgnoringResult(env, gnssMeasurementsEventBuilderObject,
+                                   method_gnssMeasurementsEventBuilderSetMeasurements,
+                                   measurementArray);
+    callObjectMethodIgnoringResult(env, gnssMeasurementsEventBuilderObject,
+                                   method_gnssMeasurementsEventBuilderSetGnssAutomaticGainControls,
+                                   gnssAgcArray);
     jobject gnssMeasurementsEventObject =
             env->CallObjectMethod(gnssMeasurementsEventBuilderObject,
                                   method_gnssMeasurementsEventBuilderBuild);
@@ -408,24 +409,24 @@
                                        satellitePvt.satClockInfo.satHardwareCodeBiasMeters,
                                        satellitePvt.satClockInfo.satTimeCorrectionMeters,
                                        satellitePvt.satClockInfo.satClkDriftMps);
-            env->CallObjectMethod(satellitePvtBuilderObject,
-                                  method_satellitePvtBuilderSetPositionEcef, positionEcef);
-            env->CallObjectMethod(satellitePvtBuilderObject,
-                                  method_satellitePvtBuilderSetVelocityEcef, velocityEcef);
-            env->CallObjectMethod(satellitePvtBuilderObject, method_satellitePvtBuilderSetClockInfo,
-                                  clockInfo);
+            callObjectMethodIgnoringResult(env, satellitePvtBuilderObject,
+                                           method_satellitePvtBuilderSetPositionEcef, positionEcef);
+            callObjectMethodIgnoringResult(env, satellitePvtBuilderObject,
+                                           method_satellitePvtBuilderSetVelocityEcef, velocityEcef);
+            callObjectMethodIgnoringResult(env, satellitePvtBuilderObject,
+                                           method_satellitePvtBuilderSetClockInfo, clockInfo);
         }
 
         if (satFlags & SatellitePvt::HAS_IONO) {
-            env->CallObjectMethod(satellitePvtBuilderObject,
-                                  method_satellitePvtBuilderSetIonoDelayMeters,
-                                  satellitePvt.ionoDelayMeters);
+            callObjectMethodIgnoringResult(env, satellitePvtBuilderObject,
+                                           method_satellitePvtBuilderSetIonoDelayMeters,
+                                           satellitePvt.ionoDelayMeters);
         }
 
         if (satFlags & SatellitePvt::HAS_TROPO) {
-            env->CallObjectMethod(satellitePvtBuilderObject,
-                                  method_satellitePvtBuilderSetTropoDelayMeters,
-                                  satellitePvt.tropoDelayMeters);
+            callObjectMethodIgnoringResult(env, satellitePvtBuilderObject,
+                                           method_satellitePvtBuilderSetTropoDelayMeters,
+                                           satellitePvt.tropoDelayMeters);
         }
 
         jobject satellitePvtObject =
@@ -453,17 +454,19 @@
             jobject correlationVectorBuilderObject =
                     env->NewObject(class_correlationVectorBuilder,
                                    method_correlationVectorBuilderCtor);
-            env->CallObjectMethod(correlationVectorBuilderObject,
-                                  method_correlationVectorBuilderSetMagnitude, magnitudeArray);
-            env->CallObjectMethod(correlationVectorBuilderObject,
-                                  method_correlationVectorBuilderSetFrequencyOffsetMetersPerSecond,
-                                  correlationVector.frequencyOffsetMps);
-            env->CallObjectMethod(correlationVectorBuilderObject,
-                                  method_correlationVectorBuilderSetSamplingStartMeters,
-                                  correlationVector.samplingStartM);
-            env->CallObjectMethod(correlationVectorBuilderObject,
-                                  method_correlationVectorBuilderSetSamplingWidthMeters,
-                                  correlationVector.samplingWidthM);
+            callObjectMethodIgnoringResult(env, correlationVectorBuilderObject,
+                                           method_correlationVectorBuilderSetMagnitude,
+                                           magnitudeArray);
+            callObjectMethodIgnoringResult(
+                    env, correlationVectorBuilderObject,
+                    method_correlationVectorBuilderSetFrequencyOffsetMetersPerSecond,
+                    correlationVector.frequencyOffsetMps);
+            callObjectMethodIgnoringResult(env, correlationVectorBuilderObject,
+                                           method_correlationVectorBuilderSetSamplingStartMeters,
+                                           correlationVector.samplingStartM);
+            callObjectMethodIgnoringResult(env, correlationVectorBuilderObject,
+                                           method_correlationVectorBuilderSetSamplingWidthMeters,
+                                           correlationVector.samplingWidthM);
             jobject correlationVectorObject =
                     env->CallObjectMethod(correlationVectorBuilderObject,
                                           method_correlationVectorBuilderBuild);
@@ -519,12 +522,14 @@
         const GnssAgc& gnssAgc = agcs[i];
 
         jobject agcBuilderObject = env->NewObject(class_gnssAgcBuilder, method_gnssAgcBuilderCtor);
-        env->CallObjectMethod(agcBuilderObject, method_gnssAgcBuilderSetLevelDb,
-                              gnssAgc.agcLevelDb);
-        env->CallObjectMethod(agcBuilderObject, method_gnssAgcBuilderSetConstellationType,
-                              (int)gnssAgc.constellation);
-        env->CallObjectMethod(agcBuilderObject, method_gnssAgcBuilderSetCarrierFrequencyHz,
-                              gnssAgc.carrierFrequencyHz);
+        callObjectMethodIgnoringResult(env, agcBuilderObject, method_gnssAgcBuilderSetLevelDb,
+                                       gnssAgc.agcLevelDb);
+        callObjectMethodIgnoringResult(env, agcBuilderObject,
+                                       method_gnssAgcBuilderSetConstellationType,
+                                       (int)gnssAgc.constellation);
+        callObjectMethodIgnoringResult(env, agcBuilderObject,
+                                       method_gnssAgcBuilderSetCarrierFrequencyHz,
+                                       gnssAgc.carrierFrequencyHz);
         jobject agcObject = env->CallObjectMethod(agcBuilderObject, method_gnssAgcBuilderBuild);
 
         env->SetObjectArrayElement(gnssAgcArray, i, agcObject);
diff --git a/services/core/jni/gnss/Utils.cpp b/services/core/jni/gnss/Utils.cpp
index 40a94ce..8f32c47 100644
--- a/services/core/jni/gnss/Utils.cpp
+++ b/services/core/jni/gnss/Utils.cpp
@@ -111,6 +111,13 @@
     }
 }
 
+void callObjectMethodIgnoringResult(JNIEnv* env, jobject obj, jmethodID mid, ...) {
+    va_list args;
+    va_start(args, mid);
+    env->DeleteLocalRef(env->CallObjectMethodV(obj, mid, args));
+    va_end(args);
+}
+
 JavaObject::JavaObject(JNIEnv* env, jclass clazz, jmethodID defaultCtor)
       : env_(env), clazz_(clazz) {
     object_ = env_->NewObject(clazz_, defaultCtor);
diff --git a/services/core/jni/gnss/Utils.h b/services/core/jni/gnss/Utils.h
index 2640a77..c8ee661 100644
--- a/services/core/jni/gnss/Utils.h
+++ b/services/core/jni/gnss/Utils.h
@@ -56,6 +56,8 @@
 
 void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName);
 
+void callObjectMethodIgnoringResult(JNIEnv* env, jobject obj, jmethodID mid, ...);
+
 template <class T>
 void logHidlError(hardware::Return<T>& result, const char* errorMessage) {
     ALOGE("%s HIDL transport error: %s", errorMessage, result.description().c_str());
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 3bda7bf0..ee64a73 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -17680,6 +17680,13 @@
                     ? Collections.emptySet()
                     : mOverlayPackagesProvider.getNonRequiredApps(
                             admin, caller.getUserId(), ACTION_PROVISION_MANAGED_PROFILE);
+            if (nonRequiredApps.isEmpty()) {
+                Slogf.i(LOG_TAG, "No disallowed packages for the managed profile.");
+            } else {
+                for (String packageName : nonRequiredApps) {
+                    Slogf.i(LOG_TAG, "Disallowed package [" + packageName + "]");
+                }
+            }
             userInfo = mUserManager.createProfileForUserEvenWhenDisallowed(
                     provisioningParams.getProfileName(),
                     UserManager.USER_TYPE_PROFILE_MANAGED,
@@ -17698,6 +17705,8 @@
                     startTime,
                     callerPackage);
 
+            onCreateAndProvisionManagedProfileStarted(provisioningParams);
+
             installExistingAdminPackage(userInfo.id, admin.getPackageName());
             if (!enableAdminAndSetProfileOwner(
                     userInfo.id, caller.getUserId(), admin, provisioningParams.getOwnerName())) {
@@ -17718,6 +17727,8 @@
                 }
             }
 
+            onCreateAndProvisionManagedProfileCompleted(provisioningParams);
+
             sendProvisioningCompletedBroadcast(
                     userInfo.id,
                     ACTION_PROVISION_MANAGED_PROFILE,
@@ -17739,6 +17750,29 @@
         }
     }
 
+    /**
+     * Callback called at the beginning of {@link #createAndProvisionManagedProfile(
+     * ManagedProfileProvisioningParams, String)} after the relevant prechecks have passed.
+     *
+     * <p>The logic in this method blocks provisioning.
+     *
+     * <p>This method is meant to be overridden by OEMs.
+     */
+    private void onCreateAndProvisionManagedProfileStarted(
+            ManagedProfileProvisioningParams provisioningParams) {}
+
+    /**
+     * Callback called at the end of {@link #createAndProvisionManagedProfile(
+     * ManagedProfileProvisioningParams, String)} after all the other provisioning tasks
+     * have completed successfully.
+     *
+     * <p>The logic in this method blocks provisioning.
+     *
+     * <p>This method is meant to be overridden by OEMs.
+     */
+    private void onCreateAndProvisionManagedProfileCompleted(
+            ManagedProfileProvisioningParams provisioningParams) {}
+
     private void resetInteractAcrossProfilesAppOps() {
         mInjector.getCrossProfileApps().clearInteractAcrossProfilesAppOps();
         pregrantDefaultInteractAcrossProfilesAppOps();
@@ -17978,6 +18012,7 @@
                         ERROR_PRE_CONDITION_FAILED,
                         "Provisioning preconditions failed with result: " + result);
             }
+            onProvisionFullyManagedDeviceStarted(provisioningParams);
             setTimeAndTimezone(provisioningParams.getTimeZone(), provisioningParams.getLocalTime());
             setLocale(provisioningParams.getLocale());
 
@@ -18003,6 +18038,7 @@
             disallowAddUser();
             setAdminCanGrantSensorsPermissionForUserUnchecked(deviceOwnerUserId,
                     provisioningParams.canDeviceOwnerGrantSensorsPermissions());
+            onProvisionFullyManagedDeviceCompleted(provisioningParams);
             sendProvisioningCompletedBroadcast(
                     deviceOwnerUserId,
                     ACTION_PROVISION_MANAGED_DEVICE,
@@ -18018,6 +18054,29 @@
         }
     }
 
+    /**
+     * Callback called at the beginning of {@link #provisionFullyManagedDevice(
+     * FullyManagedDeviceProvisioningParams, String)} after the relevant prechecks have passed.
+     *
+     * <p>The logic in this method blocks provisioning.
+     *
+     * <p>This method is meant to be overridden by OEMs.
+     */
+    private void onProvisionFullyManagedDeviceStarted(
+            FullyManagedDeviceProvisioningParams provisioningParams) {}
+
+    /**
+     * Callback called at the end of {@link #provisionFullyManagedDevice(
+     * FullyManagedDeviceProvisioningParams, String)} after all the other provisioning tasks
+     * have completed successfully.
+     *
+     * <p>The logic in this method blocks provisioning.
+     *
+     * <p>This method is meant to be overridden by OEMs.
+     */
+    private void onProvisionFullyManagedDeviceCompleted(
+            FullyManagedDeviceProvisioningParams provisioningParams) {}
+
     private void setTimeAndTimezone(String timeZone, long localTime) {
         try {
             final AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index aad1bf8..c9aeabd 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -176,6 +176,7 @@
 import com.android.server.powerstats.PowerStatsService;
 import com.android.server.profcollect.ProfcollectForwardingService;
 import com.android.server.recoverysystem.RecoverySystemService;
+import com.android.server.resources.ResourcesManagerService;
 import com.android.server.restrictions.RestrictionsManagerService;
 import com.android.server.role.RoleServicePlatformHelper;
 import com.android.server.rotationresolver.RotationResolverManagerService;
@@ -1285,6 +1286,13 @@
         mSystemServiceManager.startService(new OverlayManagerService(mSystemContext));
         t.traceEnd();
 
+        // Manages Resources packages
+        t.traceBegin("StartResourcesManagerService");
+        ResourcesManagerService resourcesService = new ResourcesManagerService(mSystemContext);
+        resourcesService.setActivityManagerService(mActivityManagerService);
+        mSystemServiceManager.startService(resourcesService);
+        t.traceEnd();
+
         t.traceBegin("StartSensorPrivacyService");
         mSystemServiceManager.startService(new SensorPrivacyService(mSystemContext));
         t.traceEnd();
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
index d5e4710..317a51b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
@@ -26,6 +26,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -33,6 +34,7 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.verifyZeroInteractions;
 
+import android.Manifest;
 import android.annotation.Nullable;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityTaskManager;
@@ -83,6 +85,7 @@
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.Objects;
 
 
 /**
@@ -121,7 +124,7 @@
     private WindowManagerInternal mMockWindowManagerInternal;
     @Mock
     private IActivityManager mMockActivityManager;
-    private FakeContext mFakeContext;
+    private MockContext mMockContext;
     private FakeGameClassifier mFakeGameClassifier;
     private FakeGameService mFakeGameService;
     private FakeServiceConnector<IGameService> mFakeGameServiceConnector;
@@ -140,7 +143,7 @@
                 .strictness(Strictness.LENIENT)
                 .startMocking();
 
-        mFakeContext = new FakeContext(InstrumentationRegistry.getInstrumentation().getContext());
+        mMockContext = new MockContext(InstrumentationRegistry.getInstrumentation().getContext());
 
         mFakeGameClassifier = new FakeGameClassifier();
         mFakeGameClassifier.recordGamePackage(GAME_A_PACKAGE);
@@ -169,7 +172,7 @@
         mGameServiceProviderInstance = new GameServiceProviderInstanceImpl(
                 new UserHandle(USER_ID),
                 ConcurrentUtils.DIRECT_EXECUTOR,
-                mFakeContext,
+                mMockContext,
                 mFakeGameClassifier,
                 mMockActivityManager,
                 mMockActivityTaskManager,
@@ -301,6 +304,7 @@
             throws Exception {
         mGameServiceProviderInstance.start();
 
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         assertThat(mFakeGameSessionService.getCapturedCreateInvocations()).isEmpty();
@@ -322,6 +326,7 @@
         mGameServiceProviderInstance.start();
         startTask(10, GAME_A_MAIN_ACTIVITY);
 
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSessionService.CapturedCreateInvocation capturedCreateInvocation =
@@ -336,6 +341,7 @@
         mGameServiceProviderInstance.start();
         dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY);
 
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         assertThat(mFakeGameSessionService.getCapturedCreateInvocations()).isEmpty();
@@ -345,6 +351,7 @@
     public void gameTaskStartedAndSessionRequested_createsGameSession() throws Exception {
         mGameServiceProviderInstance.start();
         startTask(10, GAME_A_MAIN_ACTIVITY);
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -362,7 +369,9 @@
         mGameServiceProviderInstance.start();
         startTask(10, GAME_A_MAIN_ACTIVITY);
 
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         CreateGameSessionRequest expectedCreateGameSessionRequest = new CreateGameSessionRequest(10,
@@ -376,6 +385,7 @@
     public void gameSessionSuccessfullyCreated_createsTaskOverlay() throws Exception {
         mGameServiceProviderInstance.start();
         startTask(10, GAME_A_MAIN_ACTIVITY);
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -391,6 +401,7 @@
     public void gameTaskFocused_propagatedToGameSession() throws Exception {
         mGameServiceProviderInstance.start();
         startTask(10, GAME_A_MAIN_ACTIVITY);
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -416,6 +427,7 @@
 
         mGameServiceProviderInstance.start();
         startTask(10, GAME_A_MAIN_ACTIVITY);
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -432,6 +444,7 @@
         mGameServiceProviderInstance.start();
 
         startTask(10, GAME_A_MAIN_ACTIVITY);
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         dispatchTaskRemoved(10);
@@ -449,6 +462,7 @@
         mGameServiceProviderInstance.start();
 
         startTask(10, GAME_A_MAIN_ACTIVITY);
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -466,6 +480,7 @@
         mGameServiceProviderInstance.start();
 
         startTask(10, GAME_A_MAIN_ACTIVITY);
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -486,6 +501,7 @@
         mGameServiceProviderInstance.start();
 
         startTask(10, GAME_A_MAIN_ACTIVITY);
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -513,6 +529,7 @@
         startTask(10, GAME_A_MAIN_ACTIVITY);
         startTask(11, GAME_A_MAIN_ACTIVITY);
 
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -530,6 +547,7 @@
         mGameServiceProviderInstance.start();
 
         startTask(10, GAME_A_MAIN_ACTIVITY);
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -557,6 +575,7 @@
         mGameServiceProviderInstance.start();
 
         startTask(10, GAME_A_MAIN_ACTIVITY);
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -586,6 +605,7 @@
         mGameServiceProviderInstance.start();
 
         startTask(10, GAME_A_MAIN_ACTIVITY);
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -619,10 +639,19 @@
     }
 
     @Test
+    public void createGameSession_failurePermissionDenied() throws Exception {
+        mGameServiceProviderInstance.start();
+        startTask(10, GAME_A_MAIN_ACTIVITY);
+        mockPermissionDenied(Manifest.permission.MANAGE_GAME_ACTIVITY);
+        assertThrows(SecurityException.class, () -> mFakeGameService.requestCreateGameSession(10));
+    }
+
+    @Test
     public void stop_severalActiveGameSessions_destroysGameSessionsAndUnbinds() throws Exception {
         mGameServiceProviderInstance.start();
 
         startTask(10, GAME_A_MAIN_ACTIVITY);
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -650,6 +679,7 @@
     public void takeScreenshot_failureNoBitmapCaptured() throws Exception {
         mGameServiceProviderInstance.start();
         startTask(10, GAME_A_MAIN_ACTIVITY);
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         IGameSessionController gameSessionController = getOnlyElement(
@@ -669,6 +699,7 @@
 
         mGameServiceProviderInstance.start();
         startTask(10, GAME_A_MAIN_ACTIVITY);
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         IGameSessionController gameSessionController = getOnlyElement(
@@ -683,6 +714,7 @@
 
     @Test
     public void restartGame_taskIdAssociatedWithGame_restartsTargetGame() throws Exception {
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         Intent launchIntent = new Intent("com.test.ACTION_LAUNCH_GAME_PACKAGE")
                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         when(mMockPackageManager.getLaunchIntentForPackage(GAME_A_PACKAGE))
@@ -691,6 +723,7 @@
         mGameServiceProviderInstance.start();
 
         startTask(10, GAME_A_MAIN_ACTIVITY);
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -710,14 +743,16 @@
                 .mGameSessionController.restartGame(10);
 
         verify(mMockActivityManager).forceStopPackage(GAME_A_PACKAGE, UserHandle.USER_CURRENT);
-        assertThat(mFakeContext.getLastStartedIntent()).isEqualTo(launchIntent);
+        assertThat(mMockContext.getLastStartedIntent()).isEqualTo(launchIntent);
     }
 
     @Test
     public void restartGame_taskIdNotAssociatedWithGame_noOp() throws Exception {
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mGameServiceProviderInstance.start();
 
         startTask(10, GAME_A_MAIN_ACTIVITY);
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -730,7 +765,20 @@
                 .mGameSessionController.restartGame(11);
 
         verifyZeroInteractions(mMockActivityManager);
-        assertThat(mFakeContext.getLastStartedIntent()).isNull();
+        assertThat(mMockContext.getLastStartedIntent()).isNull();
+    }
+
+    @Test
+    public void restartGame_failurePermissionDenied() throws Exception {
+        mGameServiceProviderInstance.start();
+        startTask(10, GAME_A_MAIN_ACTIVITY);
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
+        mFakeGameService.requestCreateGameSession(10);
+        IGameSessionController gameSessionController = Objects.requireNonNull(getOnlyElement(
+                mFakeGameSessionService.getCapturedCreateInvocations())).mGameSessionController;
+        mockPermissionDenied(Manifest.permission.MANAGE_GAME_ACTIVITY);
+        assertThrows(SecurityException.class,
+                () -> gameSessionController.restartGame(10));
     }
 
     private void startTask(int taskId, ComponentName componentName) {
@@ -774,6 +822,14 @@
         }
     }
 
+    private void mockPermissionGranted(String permission) {
+        mMockContext.setPermission(permission, PackageManager.PERMISSION_GRANTED);
+    }
+
+    private void mockPermissionDenied(String permission) {
+        mMockContext.setPermission(permission, PackageManager.PERMISSION_DENIED);
+    }
+
     static final class FakeGameService extends IGameService.Stub {
         private IGameServiceController mGameServiceController;
 
@@ -900,13 +956,28 @@
         }
     }
 
-    private final class FakeContext extends ContextWrapper {
+    private final class MockContext extends ContextWrapper {
         private Intent mLastStartedIntent;
+        // Map of permission name -> PermissionManager.Permission_{GRANTED|DENIED} constant
+        private final HashMap<String, Integer> mMockedPermissions = new HashMap<>();
 
-        FakeContext(Context base) {
+        MockContext(Context base) {
             super(base);
         }
 
+        /**
+         * Mock checks for the specified permission, and have them behave as per {@code granted}.
+         *
+         * <p>Passing null reverts to default behavior, which does a real permission check on the
+         * test package.
+         *
+         * @param granted One of {@link PackageManager#PERMISSION_GRANTED} or
+         *                {@link PackageManager#PERMISSION_DENIED}.
+         */
+        public void setPermission(String permission, Integer granted) {
+            mMockedPermissions.put(permission, granted);
+        }
+
         @Override
         public PackageManager getPackageManager() {
             return mMockPackageManager;
@@ -919,7 +990,15 @@
 
         @Override
         public void enforceCallingPermission(String permission, @Nullable String message) {
-            // Do nothing.
+            final Integer granted = mMockedPermissions.get(permission);
+            if (granted == null) {
+                super.enforceCallingOrSelfPermission(permission, message);
+                return;
+            }
+
+            if (!granted.equals(PackageManager.PERMISSION_GRANTED)) {
+                throw new SecurityException("[Test] permission denied: " + permission);
+            }
         }
 
         Intent getLastStartedIntent() {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
index bdfdf77..64657a9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
@@ -50,7 +50,7 @@
 import android.util.SparseArray;
 
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.Preconditions;
 
@@ -101,12 +101,12 @@
         mMockitoSession = ExtendedMockito.mockitoSession()
                     .strictness(Strictness.LENIENT)
                     .mockStatic(SystemProperties.class)
-                    .mockStatic(PackageHelper.class)
+                    .mockStatic(InstallLocationUtils.class)
                     .startMocking();
 
         when(mStorageManager.supportsCheckpoint()).thenReturn(true);
         when(mStorageManager.needsCheckpoint()).thenReturn(true);
-        when(PackageHelper.getStorageManager()).thenReturn(mStorageManager);
+        when(InstallLocationUtils.getStorageManager()).thenReturn(mStorageManager);
 
         when(SystemProperties.get(eq("ro.apex.updatable"))).thenReturn("true");
         when(SystemProperties.get(eq("ro.apex.updatable"), anyString())).thenReturn("true");
diff --git a/services/tests/servicestests/src/com/android/server/DockObserverTest.java b/services/tests/servicestests/src/com/android/server/DockObserverTest.java
new file mode 100644
index 0000000..c325778
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/DockObserverTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Intent;
+import android.os.Looper;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.testing.TestableLooper;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.internal.R;
+import com.android.internal.util.test.BroadcastInterceptingContext;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.ExecutionException;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class DockObserverTest {
+
+    @Rule
+    public TestableContext mContext =
+            new TestableContext(ApplicationProvider.getApplicationContext(), null);
+
+    private final BroadcastInterceptingContext mInterceptingContext =
+            new BroadcastInterceptingContext(mContext);
+
+    BroadcastInterceptingContext.FutureIntent updateExtconDockState(DockObserver observer,
+            String extconDockState) {
+        BroadcastInterceptingContext.FutureIntent futureIntent =
+                mInterceptingContext.nextBroadcastIntent(Intent.ACTION_DOCK_EVENT);
+        observer.setDockStateFromProviderForTesting(
+                DockObserver.ExtconStateProvider.fromString(extconDockState));
+        TestableLooper.get(this).processAllMessages();
+        return futureIntent;
+    }
+
+    DockObserver observerWithMappingConfig(String[] configEntries) {
+        mContext.getOrCreateTestableResources().addOverride(
+                R.array.config_dockExtconStateMapping,
+                configEntries);
+        return new DockObserver(mInterceptingContext);
+    }
+
+    void assertDockEventIntentWithExtraThenUndock(DockObserver observer, String extconDockState,
+            int expectedExtra) throws ExecutionException, InterruptedException {
+        assertThat(updateExtconDockState(observer, extconDockState)
+                .get().getIntExtra(Intent.EXTRA_DOCK_STATE, -1))
+                .isEqualTo(expectedExtra);
+        assertThat(updateExtconDockState(observer, "DOCK=0")
+                .get().getIntExtra(Intent.EXTRA_DOCK_STATE, -1))
+                .isEqualTo(Intent.EXTRA_DOCK_STATE_UNDOCKED);
+    }
+
+    @Before
+    public void setUp() {
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+    }
+
+    @Test
+    public void testDockIntentBroadcast_onlyAfterBootReady()
+            throws ExecutionException, InterruptedException {
+        DockObserver observer = new DockObserver(mInterceptingContext);
+        BroadcastInterceptingContext.FutureIntent futureIntent =
+                updateExtconDockState(observer, "DOCK=1");
+        updateExtconDockState(observer, "DOCK=1").assertNotReceived();
+        // Last boot phase reached
+        observer.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY);
+        TestableLooper.get(this).processAllMessages();
+        assertThat(futureIntent.get().getIntExtra(Intent.EXTRA_DOCK_STATE, -1))
+                .isEqualTo(Intent.EXTRA_DOCK_STATE_DESK);
+    }
+
+    @Test
+    public void testDockIntentBroadcast_customConfigResource()
+            throws ExecutionException, InterruptedException {
+        DockObserver observer = observerWithMappingConfig(
+                new String[] {"2,KEY1=1,KEY2=2", "3,KEY3=3"});
+        observer.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY);
+
+        // Mapping should not match
+        assertDockEventIntentWithExtraThenUndock(observer, "DOCK=1",
+                Intent.EXTRA_DOCK_STATE_DESK);
+        assertDockEventIntentWithExtraThenUndock(observer, "DOCK=1\nKEY1=1",
+                Intent.EXTRA_DOCK_STATE_DESK);
+        assertDockEventIntentWithExtraThenUndock(observer, "DOCK=1\nKEY2=2",
+                Intent.EXTRA_DOCK_STATE_DESK);
+
+        // 1st mapping now matches
+        assertDockEventIntentWithExtraThenUndock(observer, "DOCK=1\nKEY2=2\nKEY1=1",
+                Intent.EXTRA_DOCK_STATE_CAR);
+
+        // 2nd mapping now matches
+        assertDockEventIntentWithExtraThenUndock(observer, "DOCK=1\nKEY3=3",
+                Intent.EXTRA_DOCK_STATE_LE_DESK);
+    }
+
+    @Test
+    public void testDockIntentBroadcast_customConfigResourceWithWildcard()
+            throws ExecutionException, InterruptedException {
+        DockObserver observer = observerWithMappingConfig(new String[] {
+                "2,KEY2=2",
+                "3,KEY3=3",
+                "4"
+        });
+        observer.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY);
+        assertDockEventIntentWithExtraThenUndock(observer, "DOCK=1\nKEY5=5",
+                Intent.EXTRA_DOCK_STATE_HE_DESK);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
index ec884f5..5746f6f 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
@@ -19,18 +19,25 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.same;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
+import android.app.StatusBarManager;
 import android.hardware.biometrics.IBiometricContextListener;
 import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.common.OperationReason;
+import android.hardware.display.AmbientDisplayConfiguration;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.logging.InstanceId;
+import com.android.internal.statusbar.ISessionListener;
 import com.android.internal.statusbar.IStatusBarService;
 
 import com.google.common.collect.ImmutableList;
@@ -56,6 +63,10 @@
 
     @Mock
     private IStatusBarService mStatusBarService;
+    @Mock
+    private ISessionListener mSessionListener;
+    @Mock
+    private AmbientDisplayConfiguration mAmbientDisplayConfiguration;
 
     private OperationContext mOpContext = new OperationContext();
     private IBiometricContextListener mListener;
@@ -63,11 +74,17 @@
 
     @Before
     public void setup() throws RemoteException {
-        mProvider = new BiometricContextProvider(mStatusBarService, null /* handler */);
+        when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true);
+        mProvider = new BiometricContextProvider(mAmbientDisplayConfiguration, mStatusBarService,
+                null /* handler */);
         ArgumentCaptor<IBiometricContextListener> captor =
                 ArgumentCaptor.forClass(IBiometricContextListener.class);
         verify(mStatusBarService).setBiometicContextListener(captor.capture());
         mListener = captor.getValue();
+        ArgumentCaptor<ISessionListener> sessionCaptor =
+                ArgumentCaptor.forClass(ISessionListener.class);
+        verify(mStatusBarService).registerSessionListener(anyInt(), sessionCaptor.capture());
+        mSessionListener = sessionCaptor.getValue();
     }
 
     @Test
@@ -76,6 +93,12 @@
         assertThat(mProvider.isAoD()).isTrue();
         mListener.onDozeChanged(false);
         assertThat(mProvider.isAoD()).isFalse();
+
+        when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(false);
+        mListener.onDozeChanged(true);
+        assertThat(mProvider.isAoD()).isFalse();
+        mListener.onDozeChanged(false);
+        assertThat(mProvider.isAoD()).isFalse();
     }
 
     @Test
@@ -112,4 +135,84 @@
         verify(emptyConsumer, never()).accept(any());
         verify(nonEmptyConsumer).accept(same(mOpContext));
     }
+
+    @Test
+    public void testSessionId() throws RemoteException {
+        final int keyguardSessionId = 10;
+        final int bpSessionId = 20;
+
+        assertThat(mProvider.getBiometricPromptSessionId()).isNull();
+        assertThat(mProvider.getKeyguardEntrySessionId()).isNull();
+
+        mSessionListener.onSessionStarted(StatusBarManager.SESSION_KEYGUARD,
+                InstanceId.fakeInstanceId(keyguardSessionId));
+
+        assertThat(mProvider.getBiometricPromptSessionId()).isNull();
+        assertThat(mProvider.getKeyguardEntrySessionId()).isEqualTo(keyguardSessionId);
+
+        mSessionListener.onSessionStarted(StatusBarManager.SESSION_BIOMETRIC_PROMPT,
+                InstanceId.fakeInstanceId(bpSessionId));
+
+        assertThat(mProvider.getBiometricPromptSessionId()).isEqualTo(bpSessionId);
+        assertThat(mProvider.getKeyguardEntrySessionId()).isEqualTo(keyguardSessionId);
+
+        mSessionListener.onSessionEnded(StatusBarManager.SESSION_KEYGUARD,
+                InstanceId.fakeInstanceId(keyguardSessionId));
+
+        assertThat(mProvider.getBiometricPromptSessionId()).isEqualTo(bpSessionId);
+        assertThat(mProvider.getKeyguardEntrySessionId()).isNull();
+
+        mSessionListener.onSessionEnded(StatusBarManager.SESSION_BIOMETRIC_PROMPT,
+                InstanceId.fakeInstanceId(bpSessionId));
+
+        assertThat(mProvider.getBiometricPromptSessionId()).isNull();
+        assertThat(mProvider.getKeyguardEntrySessionId()).isNull();
+    }
+
+    @Test
+    public void testUpdate() throws RemoteException {
+        mListener.onDozeChanged(false);
+        OperationContext context = mProvider.updateContext(mOpContext, false /* crypto */);
+
+        // default state when nothing has been set
+        assertThat(context).isSameInstanceAs(mOpContext);
+        assertThat(mOpContext.id).isEqualTo(0);
+        assertThat(mOpContext.reason).isEqualTo(OperationReason.UNKNOWN);
+        assertThat(mOpContext.isAoD).isEqualTo(false);
+        assertThat(mOpContext.isCrypto).isEqualTo(false);
+
+        for (int type : List.of(StatusBarManager.SESSION_BIOMETRIC_PROMPT,
+                StatusBarManager.SESSION_KEYGUARD)) {
+            final int id = 40 + type;
+            final boolean aod = (type & 1) == 0;
+
+            mListener.onDozeChanged(aod);
+            mSessionListener.onSessionStarted(type, InstanceId.fakeInstanceId(id));
+            context = mProvider.updateContext(mOpContext, false /* crypto */);
+            assertThat(context).isSameInstanceAs(mOpContext);
+            assertThat(mOpContext.id).isEqualTo(id);
+            assertThat(mOpContext.reason).isEqualTo(reason(type));
+            assertThat(mOpContext.isAoD).isEqualTo(aod);
+            assertThat(mOpContext.isCrypto).isEqualTo(false);
+
+            mSessionListener.onSessionEnded(type, InstanceId.fakeInstanceId(id));
+        }
+
+        context = mProvider.updateContext(mOpContext, false /* crypto */);
+        assertThat(context).isSameInstanceAs(mOpContext);
+        assertThat(mOpContext.id).isEqualTo(0);
+        assertThat(mOpContext.reason).isEqualTo(OperationReason.UNKNOWN);
+        assertThat(mOpContext.isAoD).isEqualTo(false);
+        assertThat(mOpContext.isCrypto).isEqualTo(false);
+    }
+
+    private static byte reason(int type) {
+        if (type == StatusBarManager.SESSION_BIOMETRIC_PROMPT) {
+            return OperationReason.BIOMETRIC_PROMPT;
+        }
+        if (type == StatusBarManager.SESSION_KEYGUARD) {
+            return OperationReason.KEYGUARD;
+        }
+        return OperationReason.UNKNOWN;
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
index b0eb810..fe023374 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
@@ -31,6 +31,7 @@
 import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
 import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.common.OperationContext;
 import android.hardware.input.InputSensorInfo;
 import android.platform.test.annotations.Presubmit;
 import android.testing.TestableContext;
@@ -68,10 +69,12 @@
     @Mock
     private BaseClientMonitor mClient;
 
+    private OperationContext mOpContext;
     private BiometricLogger mLogger;
 
     @Before
     public void setUp() {
+        mOpContext = new OperationContext();
         mContext.addMockSystemService(SensorManager.class, mSensorManager);
         when(mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT)).thenReturn(
                 new Sensor(new InputSensorInfo("", "", 0, 0, Sensor.TYPE_LIGHT, 0, 0, 0, 0, 0, 0,
@@ -93,14 +96,13 @@
 
         final int acquiredInfo = 2;
         final int vendorCode = 3;
-        final boolean isCrypto = true;
         final int targetUserId = 9;
 
-        mLogger.logOnAcquired(mContext, acquiredInfo, vendorCode, isCrypto, targetUserId);
+        mLogger.logOnAcquired(mContext, mOpContext, acquiredInfo, vendorCode, targetUserId);
 
-        verify(mSink).acquired(
+        verify(mSink).acquired(eq(mOpContext),
                 eq(DEFAULT_MODALITY), eq(DEFAULT_ACTION), eq(DEFAULT_CLIENT), anyBoolean(),
-                eq(acquiredInfo), eq(vendorCode), eq(isCrypto), eq(targetUserId));
+                eq(acquiredInfo), eq(vendorCode), eq(targetUserId));
     }
 
     @Test
@@ -109,17 +111,16 @@
 
         final boolean authenticated = true;
         final boolean requireConfirmation = false;
-        final boolean isCrypto = false;
         final int targetUserId = 11;
         final boolean isBiometricPrompt = true;
 
-        mLogger.logOnAuthenticated(mContext,
-                authenticated, requireConfirmation, isCrypto, targetUserId, isBiometricPrompt);
+        mLogger.logOnAuthenticated(mContext, mOpContext,
+                authenticated, requireConfirmation, targetUserId, isBiometricPrompt);
 
-        verify(mSink).authenticate(
+        verify(mSink).authenticate(eq(mOpContext),
                 eq(DEFAULT_MODALITY), eq(DEFAULT_ACTION), eq(DEFAULT_CLIENT), anyBoolean(),
-                anyLong(), eq(authenticated), anyInt(), eq(requireConfirmation), eq(isCrypto),
-                eq(targetUserId), eq(isBiometricPrompt), anyFloat());
+                anyLong(), anyInt(), eq(requireConfirmation),
+                eq(targetUserId), anyFloat());
     }
 
     @Test
@@ -143,14 +144,13 @@
 
         final int error = 7;
         final int vendorCode = 11;
-        final boolean isCrypto = false;
         final int targetUserId = 9;
 
-        mLogger.logOnError(mContext, error, vendorCode, isCrypto, targetUserId);
+        mLogger.logOnError(mContext, mOpContext, error, vendorCode, targetUserId);
 
-        verify(mSink).error(
+        verify(mSink).error(eq(mOpContext),
                 eq(DEFAULT_MODALITY), eq(DEFAULT_ACTION), eq(DEFAULT_CLIENT), anyBoolean(),
-                anyLong(), eq(error), eq(vendorCode), eq(isCrypto), eq(targetUserId));
+                anyLong(), eq(error), eq(vendorCode), eq(targetUserId));
     }
 
     @Test
@@ -175,38 +175,34 @@
 
     private void testDisabledMetrics(boolean isBadConfig) {
         mLogger.disableMetrics();
-        mLogger.logOnAcquired(mContext,
+        mLogger.logOnAcquired(mContext, mOpContext,
                 0 /* acquiredInfo */,
                 1 /* vendorCode */,
-                true /* isCrypto */,
                 8 /* targetUserId */);
-        mLogger.logOnAuthenticated(mContext,
+        mLogger.logOnAuthenticated(mContext, mOpContext,
                 true /* authenticated */,
                 true /* requireConfirmation */,
-                false /* isCrypto */,
                 4 /* targetUserId */,
                 true/* isBiometricPrompt */);
         mLogger.logOnEnrolled(2 /* targetUserId */,
                 10 /* latency */,
                 true /* enrollSuccessful */);
-        mLogger.logOnError(mContext,
+        mLogger.logOnError(mContext, mOpContext,
                 4 /* error */,
                 0 /* vendorCode */,
-                false /* isCrypto */,
                 6 /* targetUserId */);
 
-        verify(mSink, never()).acquired(
+        verify(mSink, never()).acquired(eq(mOpContext),
                 anyInt(), anyInt(), anyInt(), anyBoolean(),
-                anyInt(), anyInt(), anyBoolean(), anyInt());
-        verify(mSink, never()).authenticate(
+                anyInt(), anyInt(), anyInt());
+        verify(mSink, never()).authenticate(eq(mOpContext),
                 anyInt(), anyInt(), anyInt(), anyBoolean(),
-                anyLong(), anyBoolean(), anyInt(), anyBoolean(),
-                anyBoolean(), anyInt(), anyBoolean(), anyFloat());
+                anyLong(), anyInt(), anyBoolean(), anyInt(), anyFloat());
         verify(mSink, never()).enroll(
                 anyInt(), anyInt(), anyInt(), anyInt(), anyLong(), anyBoolean(), anyFloat());
-        verify(mSink, never()).error(
+        verify(mSink, never()).error(eq(mOpContext),
                 anyInt(), anyInt(), anyInt(), anyBoolean(),
-                anyLong(), anyInt(), anyInt(), anyBoolean(), anyInt());
+                anyLong(), anyInt(), anyInt(), anyInt());
 
         mLogger.logUnknownEnrollmentInFramework();
         mLogger.logUnknownEnrollmentInHal();
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
index 25585dd..aba93b0 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
@@ -16,12 +16,13 @@
 
 package com.android.server.biometrics.sensors.face.aidl;
 
-import static com.google.common.truth.Truth.assertThat;
-
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.same;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -47,6 +48,7 @@
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
@@ -57,7 +59,6 @@
 
     private static final int USER_ID = 12;
     private static final long OP_ID = 32;
-    private static final boolean HAS_AOD = true;
 
     @Rule
     public final TestableContext mContext = new TestableContext(
@@ -89,7 +90,8 @@
 
     @Before
     public void setup() {
-        when(mBiometricContext.isAoD()).thenReturn(HAS_AOD);
+        when(mBiometricContext.updateContext(any(), anyBoolean())).thenAnswer(
+                i -> i.getArgument(0));
     }
 
     @Test
@@ -106,11 +108,12 @@
         final FaceAuthenticationClient client = createClient(2);
         client.start(mCallback);
 
-        verify(mHal).authenticateWithContext(eq(OP_ID), mOperationContextCaptor.capture());
+        InOrder order = inOrder(mHal, mBiometricContext);
+        order.verify(mBiometricContext).updateContext(
+                mOperationContextCaptor.capture(), anyBoolean());
+        order.verify(mHal).authenticateWithContext(
+                eq(OP_ID), same(mOperationContextCaptor.getValue()));
         verify(mHal, never()).authenticate(anyLong());
-
-        final OperationContext opContext = mOperationContextCaptor.getValue();
-        assertThat(opContext.isAoD).isEqualTo(HAS_AOD);
     }
 
     private FaceAuthenticationClient createClient(int version) throws RemoteException {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
index 6c72ebf..25135c6 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
@@ -16,10 +16,11 @@
 
 package com.android.server.biometrics.sensors.face.aidl;
 
-import static com.google.common.truth.Truth.assertThat;
-
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.same;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -43,6 +44,7 @@
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
@@ -52,7 +54,6 @@
 public class FaceDetectClientTest {
 
     private static final int USER_ID = 12;
-    private static final boolean HAS_AOD = true;
 
     @Rule
     public final TestableContext mContext = new TestableContext(
@@ -80,7 +81,8 @@
 
     @Before
     public void setup() {
-        when(mBiometricContext.isAoD()).thenReturn(HAS_AOD);
+        when(mBiometricContext.updateContext(any(), anyBoolean())).thenAnswer(
+                i -> i.getArgument(0));
     }
 
     @Test
@@ -97,11 +99,11 @@
         final FaceDetectClient client = createClient(2);
         client.start(mCallback);
 
-        verify(mHal).detectInteractionWithContext(mOperationContextCaptor.capture());
+        InOrder order = inOrder(mHal, mBiometricContext);
+        order.verify(mBiometricContext).updateContext(
+                mOperationContextCaptor.capture(), anyBoolean());
+        order.verify(mHal).detectInteractionWithContext(same(mOperationContextCaptor.getValue()));
         verify(mHal, never()).detectInteraction();
-
-        final OperationContext opContext = mOperationContextCaptor.getValue();
-        assertThat(opContext.isAoD).isEqualTo(HAS_AOD);
     }
 
     private FaceDetectClient createClient(int version) throws RemoteException {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
index 22070e9..38e048b 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
@@ -17,11 +17,15 @@
 package com.android.server.biometrics.sensors.face.aidl;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyByte;
+import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.same;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.hardware.biometrics.common.OperationContext;
 import android.hardware.biometrics.face.ISession;
 import android.hardware.face.Face;
 import android.os.IBinder;
@@ -38,8 +42,12 @@
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
@@ -71,10 +79,18 @@
     private ClientMonitorCallback mCallback;
     @Mock
     private Sensor.HalSessionCallback mHalSessionCallback;
+    @Captor
+    private ArgumentCaptor<OperationContext> mOperationContextCaptor;
 
     @Rule
     public final MockitoRule mockito = MockitoJUnit.rule();
 
+    @Before
+    public void setup() {
+        when(mBiometricContext.updateContext(any(), anyBoolean())).thenAnswer(
+                i -> i.getArgument(0));
+    }
+
     @Test
     public void enrollNoContext_v1() throws RemoteException {
         final FaceEnrollClient client = createClient(1);
@@ -89,7 +105,11 @@
         final FaceEnrollClient client = createClient(2);
         client.start(mCallback);
 
-        verify(mHal).enrollWithContext(any(), anyByte(), any(), any(), any());
+        InOrder order = inOrder(mHal, mBiometricContext);
+        order.verify(mBiometricContext).updateContext(
+                mOperationContextCaptor.capture(), anyBoolean());
+        order.verify(mHal).enrollWithContext(any(), anyByte(), any(), any(),
+                same(mOperationContextCaptor.getValue()));
         verify(mHal, never()).enroll(any(), anyByte(), any(), any());
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
index 0ac00aa..12b8264 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
@@ -37,6 +37,7 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
+import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.HalClientMonitor;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
@@ -60,6 +61,8 @@
     private UserManager mUserManager;
     @Mock
     private IFace mDaemon;
+    @Mock
+    private BiometricContext mBiometricContext;
 
     private SensorProps[] mSensorProps;
     private LockoutResetDispatcher mLockoutResetDispatcher;
@@ -89,7 +92,7 @@
         mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
 
         mFaceProvider = new TestableFaceProvider(mDaemon, mContext, mSensorProps, TAG,
-                mLockoutResetDispatcher);
+                mLockoutResetDispatcher, mBiometricContext);
     }
 
     @SuppressWarnings("rawtypes")
@@ -139,8 +142,9 @@
                 @NonNull Context context,
                 @NonNull SensorProps[] props,
                 @NonNull String halInstanceName,
-                @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
-            super(context, props, halInstanceName, lockoutResetDispatcher);
+                @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+                @NonNull BiometricContext biometricContext) {
+            super(context, props, halInstanceName, lockoutResetDispatcher, biometricContext);
             mDaemon = daemon;
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
index 21a7a8a..116d2d5 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
@@ -41,6 +41,7 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
+import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 
@@ -70,6 +71,8 @@
     private UserManager mUserManager;
     @Mock
     private BiometricScheduler mScheduler;
+    @Mock
+    private BiometricContext mBiometricContext;
 
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private LockoutResetDispatcher mLockoutResetDispatcher;
@@ -100,7 +103,8 @@
                 resetLockoutRequiresChallenge);
 
         Face10.sSystemClock = Clock.fixed(Instant.ofEpochMilli(100), ZoneId.of("PST"));
-        mFace10 = new Face10(mContext, sensorProps, mLockoutResetDispatcher, mHandler, mScheduler);
+        mFace10 = new Face10(mContext, sensorProps, mLockoutResetDispatcher, mHandler, mScheduler,
+                mBiometricContext);
         mBinder = new Binder();
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index 5c360e9..de0f038 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -24,7 +24,9 @@
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyLong;
 import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.same;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -56,6 +58,7 @@
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
@@ -68,7 +71,6 @@
 
     private static final int USER_ID = 8;
     private static final long OP_ID = 7;
-    private static final boolean HAS_AOD = true;
     private static final int POINTER_ID = 0;
     private static final int TOUCH_X = 8;
     private static final int TOUCH_Y = 20;
@@ -107,6 +109,8 @@
     private ArgumentCaptor<OperationContext> mOperationContextCaptor;
     @Captor
     private ArgumentCaptor<PointerContext> mPointerContextCaptor;
+    @Captor
+    private ArgumentCaptor<Consumer<OperationContext>> mContextInjector;
 
     @Rule
     public final MockitoRule mockito = MockitoJUnit.rule();
@@ -115,7 +119,8 @@
     public void setup() {
         when(mBiometricLogger.createALSCallback(anyBoolean())).thenAnswer(i ->
                 new CallbackWithProbe<>(mLuxProbe, i.getArgument(0)));
-        when(mBiometricContext.isAoD()).thenReturn(HAS_AOD);
+        when(mBiometricContext.updateContext(any(), anyBoolean())).thenAnswer(
+                i -> i.getArgument(0));
     }
 
     @Test
@@ -132,11 +137,12 @@
         final FingerprintAuthenticationClient client = createClient(2);
         client.start(mCallback);
 
-        verify(mHal).authenticateWithContext(eq(OP_ID), mOperationContextCaptor.capture());
+        InOrder order = inOrder(mHal, mBiometricContext);
+        order.verify(mBiometricContext).updateContext(
+                mOperationContextCaptor.capture(), anyBoolean());
+        order.verify(mHal).authenticateWithContext(
+                eq(OP_ID), same(mOperationContextCaptor.getValue()));
         verify(mHal, never()).authenticate(anyLong());
-
-        final OperationContext opContext = mOperationContextCaptor.getValue();
-        assertThat(opContext.isAoD).isEqualTo(HAS_AOD);
     }
 
     @Test
@@ -205,6 +211,23 @@
     }
 
     @Test
+    public void notifyHalWhenContextChanges() throws RemoteException {
+        final FingerprintAuthenticationClient client = createClient();
+        client.start(mCallback);
+
+        verify(mHal).authenticateWithContext(eq(OP_ID), mOperationContextCaptor.capture());
+        OperationContext opContext = mOperationContextCaptor.getValue();
+
+        // fake an update to the context
+        verify(mBiometricContext).subscribe(eq(opContext), mContextInjector.capture());
+        mContextInjector.getValue().accept(opContext);
+        verify(mHal).onContextChanged(eq(opContext));
+
+        client.stopHalOperation();
+        verify(mBiometricContext).unsubscribe(same(opContext));
+    }
+
+    @Test
     public void showHideOverlay_cancel() throws RemoteException {
         showHideOverlay(c -> c.cancel());
     }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
index 295fe47..93cbef1 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
@@ -16,8 +16,11 @@
 
 package com.android.server.biometrics.sensors.fingerprint.aidl;
 
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.same;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -42,6 +45,7 @@
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
@@ -51,7 +55,6 @@
 public class FingerprintDetectClientTest {
 
     private static final int USER_ID = 8;
-    private static final boolean HAS_AOD = true;
 
     @Rule
     public final TestableContext mContext = new TestableContext(
@@ -81,7 +84,8 @@
 
     @Before
     public void setup() {
-        when(mBiometricContext.isAoD()).thenReturn(HAS_AOD);
+        when(mBiometricContext.updateContext(any(), anyBoolean())).thenAnswer(
+                i -> i.getArgument(0));
     }
 
     @Test
@@ -100,7 +104,10 @@
 
         client.start(mCallback);
 
-        verify(mHal).detectInteractionWithContext(mOperationContextCaptor.capture());
+        InOrder order = inOrder(mHal, mBiometricContext);
+        order.verify(mBiometricContext).updateContext(
+                mOperationContextCaptor.capture(), anyBoolean());
+        order.verify(mHal).detectInteractionWithContext(same(mOperationContextCaptor.getValue()));
         verify(mHal, never()).detectInteraction();
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
index 7e44eab..5a96f5c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
@@ -18,11 +18,15 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -44,14 +48,18 @@
 
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.log.CallbackWithProbe;
+import com.android.server.biometrics.log.Probe;
 import com.android.server.biometrics.sensors.BiometricUtils;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
@@ -96,14 +104,26 @@
     private ClientMonitorCallback mCallback;
     @Mock
     private Sensor.HalSessionCallback mHalSessionCallback;
+    @Mock
+    private Probe mLuxProbe;
     @Captor
     private ArgumentCaptor<OperationContext> mOperationContextCaptor;
     @Captor
     private ArgumentCaptor<PointerContext> mPointerContextCaptor;
+    @Captor
+    private ArgumentCaptor<Consumer<OperationContext>> mContextInjector;
 
     @Rule
     public final MockitoRule mockito = MockitoJUnit.rule();
 
+    @Before
+    public void setup() {
+        when(mBiometricLogger.createALSCallback(anyBoolean())).thenAnswer(i ->
+                new CallbackWithProbe<>(mLuxProbe, i.getArgument(0)));
+        when(mBiometricContext.updateContext(any(), anyBoolean())).thenAnswer(
+                i -> i.getArgument(0));
+    }
+
     @Test
     public void enrollNoContext_v1() throws RemoteException {
         final FingerprintEnrollClient client = createClient(1);
@@ -120,7 +140,10 @@
 
         client.start(mCallback);
 
-        verify(mHal).enrollWithContext(any(), mOperationContextCaptor.capture());
+        InOrder order = inOrder(mHal, mBiometricContext);
+        order.verify(mBiometricContext).updateContext(
+                mOperationContextCaptor.capture(), anyBoolean());
+        order.verify(mHal).enrollWithContext(any(), same(mOperationContextCaptor.getValue()));
         verify(mHal, never()).enroll(any());
     }
 
@@ -172,6 +195,41 @@
     }
 
     @Test
+    public void luxProbeWhenFingerDown() throws RemoteException {
+        final FingerprintEnrollClient client = createClient();
+        client.start(mCallback);
+
+        client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
+        verify(mLuxProbe).enable();
+
+        client.onAcquired(2, 0);
+        verify(mLuxProbe, never()).disable();
+
+        client.onPointerUp();
+        verify(mLuxProbe).disable();
+
+        client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
+        verify(mLuxProbe, times(2)).enable();
+    }
+
+    @Test
+    public void notifyHalWhenContextChanges() throws RemoteException {
+        final FingerprintEnrollClient client = createClient();
+        client.start(mCallback);
+
+        verify(mHal).enrollWithContext(any(), mOperationContextCaptor.capture());
+        OperationContext opContext = mOperationContextCaptor.getValue();
+
+        // fake an update to the context
+        verify(mBiometricContext).subscribe(eq(opContext), mContextInjector.capture());
+        mContextInjector.getValue().accept(opContext);
+        verify(mHal).onContextChanged(eq(opContext));
+
+        client.stopHalOperation();
+        verify(mBiometricContext).unsubscribe(same(opContext));
+    }
+
+    @Test
     public void showHideOverlay_cancel() throws RemoteException {
         showHideOverlay(c -> c.cancel());
     }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
index 73f1516..5a1a02e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
@@ -40,6 +40,7 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
+import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.HalClientMonitor;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
@@ -71,6 +72,8 @@
     private GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
     @Mock
     private FingerprintStateCallback mFingerprintStateCallback;
+    @Mock
+    private BiometricContext mBiometricContext;
 
     private SensorProps[] mSensorProps;
     private LockoutResetDispatcher mLockoutResetDispatcher;
@@ -105,7 +108,7 @@
 
         mFingerprintProvider = new TestableFingerprintProvider(mDaemon, mContext,
                 mFingerprintStateCallback, mSensorProps, TAG, mLockoutResetDispatcher,
-                mGestureAvailabilityDispatcher);
+                mGestureAvailabilityDispatcher, mBiometricContext);
     }
 
     @SuppressWarnings("rawtypes")
@@ -157,9 +160,10 @@
                 @NonNull SensorProps[] props,
                 @NonNull String halInstanceName,
                 @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-                @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
+                @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+                @NonNull BiometricContext biometricContext) {
             super(context, fingerprintStateCallback, props, halInstanceName, lockoutResetDispatcher,
-                    gestureAvailabilityDispatcher);
+                    gestureAvailabilityDispatcher, biometricContext);
             mDaemon = daemon;
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
index f6b9209..529f994 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
@@ -39,6 +39,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
+import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.fingerprint.FingerprintStateCallback;
@@ -70,6 +71,8 @@
     private BiometricScheduler mScheduler;
     @Mock
     private FingerprintStateCallback mFingerprintStateCallback;
+    @Mock
+    private BiometricContext mBiometricContext;
 
     private LockoutResetDispatcher mLockoutResetDispatcher;
     private Fingerprint21 mFingerprint21;
@@ -101,7 +104,7 @@
 
         mFingerprint21 = new TestableFingerprint21(mContext, mFingerprintStateCallback, sensorProps,
                 mScheduler, new Handler(Looper.getMainLooper()), mLockoutResetDispatcher,
-                mHalResultController);
+                mHalResultController, mBiometricContext);
     }
 
     @Test
@@ -126,9 +129,10 @@
                 @NonNull FingerprintSensorPropertiesInternal sensorProps,
                 @NonNull BiometricScheduler scheduler, @NonNull Handler handler,
                 @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-                @NonNull HalResultController controller) {
+                @NonNull HalResultController controller,
+                @NonNull BiometricContext biometricContext) {
             super(context, fingerprintStateCallback, sensorProps, scheduler, handler,
-                    lockoutResetDispatcher, controller);
+                    lockoutResetDispatcher, controller, biometricContext);
         }
 
         @Override
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index ceb723a..33540c8 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -160,11 +160,11 @@
         mDeviceImpl.onVirtualDisplayCreatedLocked(displayId);
         verify(mIPowerManagerMock, never()).acquireWakeLock(any(Binder.class), anyInt(),
                 nullable(String.class), nullable(String.class), nullable(WorkSource.class),
-                nullable(String.class), anyInt());
+                nullable(String.class), anyInt(), eq(null));
         TestableLooper.get(this).processAllMessages();
         verify(mIPowerManagerMock, Mockito.times(1)).acquireWakeLock(any(Binder.class), anyInt(),
                 nullable(String.class), nullable(String.class), nullable(WorkSource.class),
-                nullable(String.class), eq(displayId));
+                nullable(String.class), eq(displayId), eq(null));
     }
 
     @Test
@@ -177,7 +177,7 @@
         TestableLooper.get(this).processAllMessages();
         verify(mIPowerManagerMock, Mockito.times(1)).acquireWakeLock(any(Binder.class), anyInt(),
                 nullable(String.class), nullable(String.class), nullable(WorkSource.class),
-                nullable(String.class), eq(displayId));
+                nullable(String.class), eq(displayId), eq(null));
     }
 
     @Test
@@ -196,7 +196,7 @@
         verify(mIPowerManagerMock, Mockito.times(1)).acquireWakeLock(wakeLockCaptor.capture(),
                 anyInt(),
                 nullable(String.class), nullable(String.class), nullable(WorkSource.class),
-                nullable(String.class), eq(displayId));
+                nullable(String.class), eq(displayId), eq(null));
 
         IBinder wakeLock = wakeLockCaptor.getValue();
         mDeviceImpl.onVirtualDisplayRemovedLocked(displayId);
@@ -212,7 +212,7 @@
         verify(mIPowerManagerMock, Mockito.times(1)).acquireWakeLock(wakeLockCaptor.capture(),
                 anyInt(),
                 nullable(String.class), nullable(String.class), nullable(WorkSource.class),
-                nullable(String.class), eq(displayId));
+                nullable(String.class), eq(displayId), eq(null));
         IBinder wakeLock = wakeLockCaptor.getValue();
 
         // Close the VirtualDevice without first notifying it of the VirtualDisplay removal.
diff --git a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java b/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
index 0f3742f..ac97911 100644
--- a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
@@ -59,6 +59,8 @@
 
 @RunWith(JUnit4.class)
 public final class AmbientLuxTest {
+
+    private static final float ALLOWED_ERROR_DELTA = 0.001f;
     private static final int AMBIENT_COLOR_TYPE = 20705;
     private static final String AMBIENT_COLOR_TYPE_STR = "colorSensoryDensoryDoc";
     private static final float LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE = 5432.1f;
@@ -78,6 +80,8 @@
     @Mock private TypedArray mHighLightBiases;
     @Mock private TypedArray mAmbientColorTemperatures;
     @Mock private TypedArray mDisplayColorTemperatures;
+    @Mock private TypedArray mStrongAmbientColorTemperatures;
+    @Mock private TypedArray mStrongDisplayColorTemperatures;
     @Mock private ColorDisplayService.ColorDisplayServiceInternal mColorDisplayServiceInternalMock;
 
     @Before
@@ -110,6 +114,12 @@
         when(mResourcesSpy.obtainTypedArray(
                 R.array.config_displayWhiteBalanceDisplayColorTemperatures))
                 .thenReturn(mDisplayColorTemperatures);
+        when(mResourcesSpy.obtainTypedArray(
+                R.array.config_displayWhiteBalanceStrongAmbientColorTemperatures))
+                .thenReturn(mStrongAmbientColorTemperatures);
+        when(mResourcesSpy.obtainTypedArray(
+                R.array.config_displayWhiteBalanceStrongDisplayColorTemperatures))
+                .thenReturn(mStrongDisplayColorTemperatures);
 
         when(mResourcesSpy.obtainTypedArray(
                 R.array.config_displayWhiteBalanceLowLightAmbientBrightnesses))
@@ -375,6 +385,43 @@
     }
 
     @Test
+    public void testStrongMode() {
+        final float lowerBrightness = 10.0f;
+        final float upperBrightness = 50.0f;
+        setBrightnesses(lowerBrightness, upperBrightness);
+        setBiases(0.0f, 1.0f);
+        final int ambientColorTempLow = 6000;
+        final int ambientColorTempHigh = 8000;
+        final int displayColorTempLow = 6400;
+        final int displayColorTempHigh = 7400;
+        setStrongAmbientColorTemperatures(ambientColorTempLow, ambientColorTempHigh);
+        setStrongDisplayColorTemperatures(displayColorTempLow, displayColorTempHigh);
+
+        DisplayWhiteBalanceController controller =
+                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
+        controller.setStrongModeEnabled(true);
+        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
+
+        for (float ambientTempFraction = 0.0f; ambientTempFraction <= 1.0f;
+                ambientTempFraction += 0.1f) {
+            final float ambientTemp =
+                    (ambientColorTempHigh - ambientColorTempLow) * ambientTempFraction
+                            + ambientColorTempLow;
+            setEstimatedColorTemperature(controller, ambientTemp);
+            for (float brightnessFraction = 0.0f; brightnessFraction <= 1.0f;
+                    brightnessFraction += 0.1f) {
+                setEstimatedBrightnessAndUpdate(controller,
+                        mix(lowerBrightness, upperBrightness, brightnessFraction));
+                assertEquals(controller.mPendingAmbientColorTemperature,
+                        mix(LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE,
+                                mix(displayColorTempLow, displayColorTempHigh, ambientTempFraction),
+                                brightnessFraction),
+                        ALLOWED_ERROR_DELTA);
+            }
+        }
+    }
+
+    @Test
     public void testLowLight_DefaultAmbient() throws Exception {
         final float lowerBrightness = 10.0f;
         final float upperBrightness = 50.0f;
@@ -486,6 +533,14 @@
         setFloatArrayResource(mDisplayColorTemperatures, vals);
     }
 
+    private void setStrongAmbientColorTemperatures(float... vals) {
+        setFloatArrayResource(mStrongAmbientColorTemperatures, vals);
+    }
+
+    private void setStrongDisplayColorTemperatures(float... vals) {
+        setFloatArrayResource(mStrongDisplayColorTemperatures, vals);
+    }
+
     private void setFloatArrayResource(TypedArray array, float[] vals) {
         when(array.length()).thenReturn(vals.length);
         for (int i = 0; i < vals.length; i++) {
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerTests.java
index b621a44..869ac88 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerTests.java
@@ -70,7 +70,7 @@
 import androidx.test.filters.Suppress;
 
 import com.android.frameworks.servicestests.R;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
 import com.android.server.pm.pkg.parsing.ParsingPackage;
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 
@@ -106,11 +106,11 @@
 
     private static final String SECURE_CONTAINERS_PREFIX = "/mnt/asec";
 
-    private static final int APP_INSTALL_AUTO = PackageHelper.APP_INSTALL_AUTO;
+    private static final int APP_INSTALL_AUTO = InstallLocationUtils.APP_INSTALL_AUTO;
 
-    private static final int APP_INSTALL_DEVICE = PackageHelper.APP_INSTALL_INTERNAL;
+    private static final int APP_INSTALL_DEVICE = InstallLocationUtils.APP_INSTALL_INTERNAL;
 
-    private static final int APP_INSTALL_SDCARD = PackageHelper.APP_INSTALL_EXTERNAL;
+    private static final int APP_INSTALL_SDCARD = InstallLocationUtils.APP_INSTALL_EXTERNAL;
 
     void failStr(String errMsg) {
         Log.w(TAG, "errMsg=" + errMsg);
@@ -1214,7 +1214,7 @@
         int origDefaultLoc = getDefaultInstallLoc();
         InstallParams ip = null;
         try {
-            setInstallLoc(PackageHelper.APP_INSTALL_AUTO);
+            setInstallLoc(InstallLocationUtils.APP_INSTALL_AUTO);
             // Install first
             ip = installFromRawResource("install.apk", rawResId, installFlags, false,
                     false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
@@ -1303,7 +1303,7 @@
         InstallParams ip = null;
         try {
             PackageManager pm = getPm();
-            setInstallLoc(PackageHelper.APP_INSTALL_AUTO);
+            setInstallLoc(InstallLocationUtils.APP_INSTALL_AUTO);
             // Install first
             ip = installFromRawResource("install.apk", R.raw.install, installFlags, false,
                     false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
@@ -1517,11 +1517,11 @@
         int iloc = PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
         boolean enable = getUserSettingSetInstallLocation();
         if (enable) {
-            if (userSetting == PackageHelper.APP_INSTALL_AUTO) {
+            if (userSetting == InstallLocationUtils.APP_INSTALL_AUTO) {
                 iloc = PackageInfo.INSTALL_LOCATION_AUTO;
-            } else if (userSetting == PackageHelper.APP_INSTALL_EXTERNAL) {
+            } else if (userSetting == InstallLocationUtils.APP_INSTALL_EXTERNAL) {
                 iloc = PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL;
-            } else if (userSetting == PackageHelper.APP_INSTALL_INTERNAL) {
+            } else if (userSetting == InstallLocationUtils.APP_INSTALL_INTERNAL) {
                 iloc = PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY;
             }
         }
@@ -1552,7 +1552,7 @@
     }
     @LargeTest
     public void testExistingIUserI() throws Exception {
-        int userSetting = PackageHelper.APP_INSTALL_INTERNAL;
+        int userSetting = InstallLocationUtils.APP_INSTALL_INTERNAL;
         int iFlags = PackageManager.INSTALL_INTERNAL;
         setExistingXUserX(userSetting, iFlags, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
     }
@@ -1564,14 +1564,14 @@
             return;
         }
 
-        int userSetting = PackageHelper.APP_INSTALL_EXTERNAL;
+        int userSetting = InstallLocationUtils.APP_INSTALL_EXTERNAL;
         int iFlags = PackageManager.INSTALL_INTERNAL;
         setExistingXUserX(userSetting, iFlags, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
     }
 
     @LargeTest
     public void testExistingIUserA() throws Exception {
-        int userSetting = PackageHelper.APP_INSTALL_AUTO;
+        int userSetting = InstallLocationUtils.APP_INSTALL_AUTO;
         int iFlags = PackageManager.INSTALL_INTERNAL;
         setExistingXUserX(userSetting, iFlags, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
     }
@@ -1616,7 +1616,7 @@
     }
     @LargeTest
     public void testUserI() throws Exception {
-        int userSetting = PackageHelper.APP_INSTALL_INTERNAL;
+        int userSetting = InstallLocationUtils.APP_INSTALL_INTERNAL;
         int iloc = getExpectedInstallLocation(userSetting);
         setUserX(true, userSetting, iloc);
     }
@@ -1628,14 +1628,14 @@
             return;
         }
 
-        int userSetting = PackageHelper.APP_INSTALL_EXTERNAL;
+        int userSetting = InstallLocationUtils.APP_INSTALL_EXTERNAL;
         int iloc = getExpectedInstallLocation(userSetting);
         setUserX(true, userSetting, iloc);
     }
 
     @LargeTest
     public void testUserA() throws Exception {
-        int userSetting = PackageHelper.APP_INSTALL_AUTO;
+        int userSetting = InstallLocationUtils.APP_INSTALL_AUTO;
         int iloc = getExpectedInstallLocation(userSetting);
         setUserX(true, userSetting, iloc);
     }
@@ -1646,7 +1646,7 @@
      */
     @LargeTest
     public void testUserPrefOffUserI() throws Exception {
-        int userSetting = PackageHelper.APP_INSTALL_INTERNAL;
+        int userSetting = InstallLocationUtils.APP_INSTALL_INTERNAL;
         int iloc = PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
         setUserX(false, userSetting, iloc);
     }
@@ -1658,14 +1658,14 @@
             return;
         }
 
-        int userSetting = PackageHelper.APP_INSTALL_EXTERNAL;
+        int userSetting = InstallLocationUtils.APP_INSTALL_EXTERNAL;
         int iloc = PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
         setUserX(false, userSetting, iloc);
     }
 
     @LargeTest
     public void testUserPrefOffA() throws Exception {
-        int userSetting = PackageHelper.APP_INSTALL_AUTO;
+        int userSetting = InstallLocationUtils.APP_INSTALL_AUTO;
         int iloc = PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
         setUserX(false, userSetting, iloc);
     }
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index 51dbd97..827349a 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -64,6 +64,7 @@
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.IWakeLockCallback;
 import android.os.Looper;
 import android.os.PowerManager;
 import android.os.PowerSaveState;
@@ -102,6 +103,7 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.ArgumentMatcher;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.mockito.stubbing.Answer;
 
@@ -479,21 +481,21 @@
         // First, ensure that a normal full wake lock does not cause a wakeup
         int flags = PowerManager.FULL_WAKE_LOCK;
         mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
-                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY);
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
         assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
         mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
 
         // Ensure that the flag does *NOT* work with a partial wake lock.
         flags = PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
         mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
-                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY);
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
         assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
         mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
 
         // Verify that flag forces a wakeup when paired to a FULL_WAKE_LOCK
         flags = PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
         mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
-                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY);
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
         assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
         mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
     }
@@ -661,12 +663,12 @@
             wakelockMap.put((String) inv.getArguments()[1], (int) inv.getArguments()[0]);
             return null;
         }).when(mNotifierMock).onWakeLockAcquired(anyInt(), anyString(), anyString(), anyInt(),
-                anyInt(), any(), any());
+                anyInt(), any(), any(), any());
         doAnswer(inv -> {
             wakelockMap.remove((String) inv.getArguments()[1]);
             return null;
         }).when(mNotifierMock).onWakeLockReleased(anyInt(), anyString(), anyString(), anyInt(),
-                anyInt(), any(), any());
+                anyInt(), any(), any(), any());
 
         //
         // TEST STARTS HERE
@@ -679,7 +681,7 @@
 
         // Create a wakelock
         mService.getBinderServiceInstance().acquireWakeLock(new Binder(), flags, tag, pkg,
-                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY);
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
         assertThat(wakelockMap.get(tag)).isEqualTo(flags);  // Verify wakelock is active.
 
         // Confirm that the wakelocks have been disabled when the forceSuspend is in flight.
@@ -737,7 +739,7 @@
         // Take a nap and verify we no longer hold the blocker
         int flags = PowerManager.DOZE_WAKE_LOCK;
         mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
-                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY);
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
         when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
         mService.getBinderServiceInstance().goToSleep(mClock.now(),
                 PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0);
@@ -893,7 +895,7 @@
 
         mService.getBinderServiceInstance().acquireWakeLock(token,
                 PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg,
-                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY);
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
 
         assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
         advanceTime(60);
@@ -919,7 +921,7 @@
 
         mService.getBinderServiceInstance().acquireWakeLock(token,
                 PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, tag, pkg,
-                null /* workSource */, null /* historyTag */, Display.DEFAULT_DISPLAY);
+                null /* workSource */, null /* historyTag */, Display.DEFAULT_DISPLAY, null);
 
         advanceTime(1500);
         mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
@@ -995,7 +997,7 @@
 
         mService.getBinderServiceInstance().acquireWakeLock(token,
                 PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg,
-                null /* workSource */, null /* historyTag */, Display.DEFAULT_DISPLAY);
+                null /* workSource */, null /* historyTag */, Display.DEFAULT_DISPLAY, null);
 
         assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
         assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
@@ -1035,7 +1037,7 @@
 
         mService.getBinderServiceInstance().acquireWakeLock(token,
                 PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg,
-                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY);
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
 
         assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
         assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
@@ -1076,7 +1078,7 @@
 
         mService.getBinderServiceInstance().acquireWakeLock(token,
                 PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg,
-                null /* workSource */, null /* historyTag */, nonDefaultDisplay);
+                null /* workSource */, null /* historyTag */, nonDefaultDisplay, null);
 
         assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
                 WAKEFULNESS_AWAKE);
@@ -1640,7 +1642,65 @@
         IBinder token = new Binder();
         String packageName = "pkg.name";
         mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
-                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY);
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY,
+                null /* callback */);
         return mService.findWakeLockLocked(token);
     }
+
+    /**
+     * Test IPowerManager.acquireWakeLock() with a IWakeLockCallback.
+     */
+    @Test
+    public void testNotifyWakeLockCallback() {
+        createService();
+        startSystem();
+        final String tag = "wakelock1";
+        final String packageName = "pkg.name";
+        final IBinder token = new Binder();
+        final int flags = PowerManager.PARTIAL_WAKE_LOCK;
+        final IWakeLockCallback callback = Mockito.mock(IWakeLockCallback.class);
+        final IBinder callbackBinder = Mockito.mock(Binder.class);
+        when(callback.asBinder()).thenReturn(callbackBinder);
+        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, callback);
+        verify(mNotifierMock).onWakeLockAcquired(anyInt(), eq(tag), eq(packageName), anyInt(),
+                anyInt(), any(), any(), same(callback));
+
+        mService.getBinderServiceInstance().releaseWakeLock(token, 0);
+        verify(mNotifierMock).onWakeLockReleased(anyInt(), eq(tag), eq(packageName), anyInt(),
+                anyInt(), any(), any(), same(callback));
+    }
+
+    /**
+     * Test IPowerManager.updateWakeLockCallback() with a new IWakeLockCallback.
+     */
+    @Test
+    public void testNotifyWakeLockCallbackChange() {
+        createService();
+        startSystem();
+        final String tag = "wakelock1";
+        final String packageName = "pkg.name";
+        final IBinder token = new Binder();
+        int flags = PowerManager.PARTIAL_WAKE_LOCK;
+        final IWakeLockCallback callback1 = Mockito.mock(IWakeLockCallback.class);
+        final IBinder callbackBinder1 = Mockito.mock(Binder.class);
+        when(callback1.asBinder()).thenReturn(callbackBinder1);
+        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, callback1);
+        verify(mNotifierMock).onWakeLockAcquired(anyInt(), eq(tag), eq(packageName), anyInt(),
+                anyInt(), any(), any(), same(callback1));
+
+        final IWakeLockCallback callback2 = Mockito.mock(IWakeLockCallback.class);
+        final IBinder callbackBinder2 = Mockito.mock(Binder.class);
+        when(callback2.asBinder()).thenReturn(callbackBinder2);
+        mService.getBinderServiceInstance().updateWakeLockCallback(token, callback2);
+        verify(mNotifierMock).onWakeLockChanging(anyInt(), eq(tag), eq(packageName),
+                anyInt(), anyInt(), any(), any(), same(callback1),
+                anyInt(), eq(tag), eq(packageName), anyInt(), anyInt(), any(), any(),
+                same(callback2));
+
+        mService.getBinderServiceInstance().releaseWakeLock(token, 0);
+        verify(mNotifierMock).onWakeLockReleased(anyInt(), eq(tag), eq(packageName), anyInt(),
+                anyInt(), any(), any(), same(callback2));
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
index fb15088..0f18cc6 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
@@ -17,7 +17,6 @@
 package com.android.server.notification;
 
 import static android.app.Notification.CATEGORY_CALL;
-import static android.app.Notification.CATEGORY_MESSAGE;
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_ANYONE;
 import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_IMPORTANT;
@@ -25,6 +24,7 @@
 import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CALLS;
 import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CONVERSATIONS;
 import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES;
+import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_REPEAT_CALLERS;
 import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
 import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
@@ -43,8 +43,10 @@
 import android.app.NotificationChannel;
 import android.app.NotificationManager.Policy;
 import android.media.AudioAttributes;
+import android.os.Bundle;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
+import android.telephony.TelephonyManager;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -68,10 +70,15 @@
     private NotificationMessagingUtil mMessagingUtil;
     private ZenModeFiltering mZenModeFiltering;
 
+    @Mock private TelephonyManager mTelephonyManager;
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mZenModeFiltering = new ZenModeFiltering(mContext, mMessagingUtil);
+
+        // for repeat callers / matchesCallFilter
+        mContext.addMockSystemService(TelephonyManager.class, mTelephonyManager);
     }
 
     private NotificationRecord getNotificationRecord() {
@@ -95,6 +102,23 @@
         return r;
     }
 
+    private Bundle makeExtrasBundleWithPeople(String[] people) {
+        Bundle extras = new Bundle();
+        extras.putObject(Notification.EXTRA_PEOPLE_LIST, people);
+        return extras;
+    }
+
+    private NotificationRecord getNotificationRecordWithPeople(String[] people) {
+        // set up notification record
+        NotificationRecord r = mock(NotificationRecord.class);
+        StatusBarNotification sbn = mock(StatusBarNotification.class);
+        Notification notification = mock(Notification.class);
+        notification.extras = makeExtrasBundleWithPeople(people);
+        when(sbn.getNotification()).thenReturn(notification);
+        when(r.getSbn()).thenReturn(sbn);
+        return r;
+    }
+
     @Test
     public void testIsMessage() {
         NotificationRecord r = getNotificationRecord();
@@ -309,4 +333,111 @@
 
         assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
     }
+
+    @Test
+    public void testMatchesCallFilter_repeatCallers_directMatch() {
+        // after calls given an email with an exact string match, make sure that
+        // matchesCallFilter returns the right thing
+        String[] mailSource = new String[]{"mailto:hello.world"};
+        mZenModeFiltering.recordCall(getNotificationRecordWithPeople(mailSource));
+
+        // set up policy to only allow repeat callers
+        Policy policy = new Policy(
+                PRIORITY_CATEGORY_REPEAT_CALLERS, 0, 0, 0, CONVERSATION_SENDERS_NONE);
+
+        // check whether matchesCallFilter returns the right thing
+        Bundle inputMatches = makeExtrasBundleWithPeople(new String[]{"mailto:hello.world"});
+        Bundle inputWrong = makeExtrasBundleWithPeople(new String[]{"mailto:nope"});
+        assertTrue(ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+                policy, UserHandle.SYSTEM,
+                inputMatches, null, 0, 0));
+        assertFalse(ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+                policy, UserHandle.SYSTEM,
+                inputWrong, null, 0, 0));
+    }
+
+    @Test
+    public void testMatchesCallFilter_repeatCallers_telephoneVariants() {
+        // set up telephony manager behavior
+        when(mTelephonyManager.getNetworkCountryIso()).thenReturn("us");
+
+        String[] telSource = new String[]{"tel:+1-617-555-1212"};
+        mZenModeFiltering.recordCall(getNotificationRecordWithPeople(telSource));
+
+        // set up policy to only allow repeat callers
+        Policy policy = new Policy(
+                PRIORITY_CATEGORY_REPEAT_CALLERS, 0, 0, 0, CONVERSATION_SENDERS_NONE);
+
+        // cases to test:
+        //   - identical number
+        //   - same number, different formatting
+        //   - different number
+        //   - garbage
+        Bundle identical = makeExtrasBundleWithPeople(new String[]{"tel:+1-617-555-1212"});
+        Bundle same = makeExtrasBundleWithPeople(new String[]{"tel:16175551212"});
+        Bundle different = makeExtrasBundleWithPeople(new String[]{"tel:123-456-7890"});
+        Bundle garbage = makeExtrasBundleWithPeople(new String[]{"asdfghjkl;"});
+
+        assertTrue("identical numbers should match",
+                ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+                policy, UserHandle.SYSTEM,
+                identical, null, 0, 0));
+        assertTrue("equivalent but non-identical numbers should match",
+                ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+                policy, UserHandle.SYSTEM,
+                same, null, 0, 0));
+        assertFalse("non-equivalent numbers should not match",
+                ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+                policy, UserHandle.SYSTEM,
+                different, null, 0, 0));
+        assertFalse("non-tel strings should not match",
+                ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+                policy, UserHandle.SYSTEM,
+                garbage, null, 0, 0));
+    }
+
+    @Test
+    public void testMatchesCallFilter_repeatCallers_urlEncodedTels() {
+        // this is not intended to be a supported case but is one that we have seen
+        // sometimes in the wild, so make sure we handle url-encoded telephone numbers correctly
+        // when somebody provides one.
+
+        // set up telephony manager behavior
+        when(mTelephonyManager.getNetworkCountryIso()).thenReturn("us");
+
+        String[] telSource = new String[]{"tel:%2B16175551212"};
+        mZenModeFiltering.recordCall(getNotificationRecordWithPeople(telSource));
+
+        // set up policy to only allow repeat callers
+        Policy policy = new Policy(
+                PRIORITY_CATEGORY_REPEAT_CALLERS, 0, 0, 0, CONVERSATION_SENDERS_NONE);
+
+        // test cases for various forms of the same phone number and different ones
+        Bundle same1 = makeExtrasBundleWithPeople(new String[]{"tel:+1-617-555-1212"});
+        Bundle same2 = makeExtrasBundleWithPeople(new String[]{"tel:%2B1-617-555-1212"});
+        Bundle same3 = makeExtrasBundleWithPeople(new String[]{"tel:6175551212"});
+        Bundle different1 = makeExtrasBundleWithPeople(new String[]{"tel:%2B16175553434"});
+        Bundle different2 = makeExtrasBundleWithPeople(new String[]{"tel:+16175553434"});
+
+        assertTrue("same number should match",
+                ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+                        policy, UserHandle.SYSTEM,
+                        same1, null, 0, 0));
+        assertTrue("same number should match",
+                ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+                        policy, UserHandle.SYSTEM,
+                        same2, null, 0, 0));
+        assertTrue("same number should match",
+                ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+                        policy, UserHandle.SYSTEM,
+                        same3, null, 0, 0));
+        assertFalse("different number should not match",
+                ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+                        policy, UserHandle.SYSTEM,
+                        different1, null, 0, 0));
+        assertFalse("different number should not match",
+                ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+                        policy, UserHandle.SYSTEM,
+                        different2, null, 0, 0));
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index 7c340ec..8ada971 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -191,7 +191,8 @@
             public void onFixedRotationFinished(int displayId) {}
 
             @Override
-            public void onKeepClearAreasChanged(int displayId, List<Rect> keepClearAreas) {}
+            public void onKeepClearAreasChanged(int displayId, List<Rect> restricted,
+                    List<Rect> unrestricted) {}
         };
         int[] displayIds = mAtm.mWindowManager.registerDisplayWindowListener(listener);
         for (int i = 0; i < displayIds.length; i++) {