Merge "Adds LOCATION_BYPASS permission."
diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
index 9b1f2d0..ddcc746 100644
--- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
@@ -216,4 +216,11 @@
     void dumpState(String[] args, PrintWriter pw);
 
     boolean isAppIdleEnabled();
+
+    /**
+     * Returns the duration (in millis) for the window where events occurring will be
+     * considered as broadcast response, starting from the point when an app receives
+     * a broadcast.
+     */
+    long getBroadcastResponseWindowDurationMs();
 }
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 8b26397..050e3df 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -347,6 +347,14 @@
      */
     boolean mLinkCrossProfileApps =
             ConstantsObserver.DEFAULT_CROSS_PROFILE_APPS_SHARE_STANDBY_BUCKETS;
+
+    /**
+     * Duration (in millis) for the window where events occurring will be considered as
+     * broadcast response, starting from the point when an app receives a broadcast.
+     */
+    volatile long mBroadcastResponseWindowDurationMillis =
+            ConstantsObserver.DEFAULT_BROADCAST_RESPONSE_WINDOW_DURATION_MS;
+
     /**
      * Whether we should allow apps into the
      * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED} bucket or not.
@@ -1774,6 +1782,10 @@
         }
     }
 
+    @Override
+    public long getBroadcastResponseWindowDurationMs() {
+        return mBroadcastResponseWindowDurationMillis;
+    }
 
     @Override
     public void flushToDisk() {
@@ -2042,6 +2054,10 @@
         TimeUtils.formatDuration(mSystemUpdateUsageTimeoutMillis, pw);
         pw.println();
 
+        pw.print("  mBroadcastResponseWindowDurationMillis=");
+        TimeUtils.formatDuration(mBroadcastResponseWindowDurationMillis, pw);
+        pw.println();
+
         pw.println();
         pw.print("mAppIdleEnabled="); pw.print(mAppIdleEnabled);
         pw.print(" mAllowRestrictedBucket=");
@@ -2473,6 +2489,8 @@
                 KEY_PREFIX_ELAPSED_TIME_THRESHOLD + "rare",
                 KEY_PREFIX_ELAPSED_TIME_THRESHOLD + "restricted"
         };
+        private static final String KEY_BROADCAST_RESPONSE_WINDOW_DURATION_MS =
+                "broadcast_response_window_timeout_ms";
         public static final long DEFAULT_CHECK_IDLE_INTERVAL_MS =
                 COMPRESS_TIME ? ONE_MINUTE : 4 * ONE_HOUR;
         public static final long DEFAULT_STRONG_USAGE_TIMEOUT =
@@ -2502,6 +2520,8 @@
         public static final long DEFAULT_AUTO_RESTRICTED_BUCKET_DELAY_MS =
                 COMPRESS_TIME ? ONE_MINUTE : ONE_DAY;
         public static final boolean DEFAULT_CROSS_PROFILE_APPS_SHARE_STANDBY_BUCKETS = true;
+        public static final long DEFAULT_BROADCAST_RESPONSE_WINDOW_DURATION_MS =
+                2 * ONE_MINUTE;
 
         ConstantsObserver(Handler handler) {
             super(handler);
@@ -2619,6 +2639,11 @@
                                     KEY_UNEXEMPTED_SYNC_SCHEDULED_HOLD_DURATION,
                                     DEFAULT_UNEXEMPTED_SYNC_SCHEDULED_TIMEOUT);
                             break;
+                        case KEY_BROADCAST_RESPONSE_WINDOW_DURATION_MS:
+                            mBroadcastResponseWindowDurationMillis = properties.getLong(
+                                    KEY_BROADCAST_RESPONSE_WINDOW_DURATION_MS,
+                                    DEFAULT_BROADCAST_RESPONSE_WINDOW_DURATION_MS);
+                            break;
                         default:
                             if (!timeThresholdsUpdated
                                     && (name.startsWith(KEY_PREFIX_SCREEN_TIME_THRESHOLD)
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
index c5dc51c..e407e31 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
+++ b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
@@ -5,7 +5,9 @@
       "options": [
         {"include-filter": "android.app.usage.cts.UsageStatsTest"},
         {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
-        {"exclude-annotation": "androidx.test.filters.FlakyTest"}
+        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+        {"exclude-annotation": "androidx.test.filters.MediumTest"},
+        {"exclude-annotation": "androidx.test.filters.LargeTest"}
       ]
     },
     {
diff --git a/api/api.go b/api/api.go
index aa9e399e..17649e8 100644
--- a/api/api.go
+++ b/api/api.go
@@ -27,7 +27,6 @@
 const art = "art.module.public.api"
 const conscrypt = "conscrypt.module.public.api"
 const i18n = "i18n.module.public.api"
-var modules_with_only_public_scope = []string{i18n, conscrypt}
 
 // The intention behind this soong plugin is to generate a number of "merged"
 // API-related modules that would otherwise require a large amount of very
@@ -149,8 +148,6 @@
 // This produces the same annotations.zip as framework-doc-stubs, but by using
 // outputs from individual modules instead of all the source code.
 func createMergedAnnotations(ctx android.LoadHookContext, modules []string) {
-	// Conscrypt and i18n currently do not enable annotations
-	modules = removeAll(modules, []string{conscrypt, i18n})
 	props := genruleProps{}
 	props.Name = proptools.StringPtr("sdk-annotations.zip")
 	props.Tools = []string{"merge_annotation_zips", "soong_zip"}
@@ -195,11 +192,8 @@
 
 func createMergedSystemStubs(ctx android.LoadHookContext, modules []string) {
 	props := libraryProps{}
-	modules_with_system_stubs := removeAll(modules, modules_with_only_public_scope)
 	props.Name = proptools.StringPtr("all-modules-system-stubs")
-	props.Static_libs = append(
-		transformArray(modules_with_only_public_scope, "", ".stubs"),
-		transformArray(modules_with_system_stubs, "", ".stubs.system")...)
+	props.Static_libs = transformArray(modules, "", ".stubs.system")
 	props.Sdk_version = proptools.StringPtr("module_current")
 	props.Visibility = []string{"//frameworks/base"}
 	ctx.CreateModule(java.LibraryFactory, &props)
@@ -226,8 +220,6 @@
 
 func createMergedTxts(ctx android.LoadHookContext, bootclasspath, system_server_classpath []string) {
 	var textFiles []MergedTxtDefinition
-	// Two module libraries currently do not support @SystemApi so only have the public scope.
-	bcpWithSystemApi := removeAll(bootclasspath, modules_with_only_public_scope)
 
 	tagSuffix := []string{".api.txt}", ".removed-api.txt}"}
 	for i, f := range []string{"current.txt", "removed.txt"} {
@@ -241,14 +233,14 @@
 		textFiles = append(textFiles, MergedTxtDefinition{
 			TxtFilename: f,
 			BaseTxt:     ":non-updatable-system-" + f,
-			Modules:     bcpWithSystemApi,
+			Modules:     bootclasspath,
 			ModuleTag:   "{.system" + tagSuffix[i],
 			Scope:       "system",
 		})
 		textFiles = append(textFiles, MergedTxtDefinition{
 			TxtFilename: f,
 			BaseTxt:     ":non-updatable-module-lib-" + f,
-			Modules:     bcpWithSystemApi,
+			Modules:     bootclasspath,
 			ModuleTag:   "{.module-lib" + tagSuffix[i],
 			Scope:       "module-lib",
 		})
diff --git a/cmds/incidentd/src/WorkDirectory.cpp b/cmds/incidentd/src/WorkDirectory.cpp
index dd33fdf..c8d2e0e 100644
--- a/cmds/incidentd/src/WorkDirectory.cpp
+++ b/cmds/incidentd/src/WorkDirectory.cpp
@@ -280,7 +280,7 @@
                 // Lower privacy policy (less restrictive) wins.
                 report->set_privacy_policy(args.getPrivacyPolicy());
             }
-            report->set_all_sections(report->all_sections() | args.all());
+            report->set_all_sections(report->all_sections() || args.all());
             for (int section: args.sections()) {
                 if (!has_section(*report, section)) {
                     report->add_section(section);
diff --git a/core/api/current.txt b/core/api/current.txt
index 8074d6c..f026116 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4183,6 +4183,7 @@
     method public void openContextMenu(android.view.View);
     method public void openOptionsMenu();
     method public void overridePendingTransition(int, int);
+    method public void overridePendingTransition(int, int, int);
     method public void postponeEnterTransition();
     method public void recreate();
     method public void registerActivityLifecycleCallbacks(@NonNull android.app.Application.ActivityLifecycleCallbacks);
@@ -4486,6 +4487,7 @@
     method public static android.app.ActivityOptions makeBasic();
     method public static android.app.ActivityOptions makeClipRevealAnimation(android.view.View, int, int, int, int);
     method public static android.app.ActivityOptions makeCustomAnimation(android.content.Context, int, int);
+    method @NonNull public static android.app.ActivityOptions makeCustomAnimation(@NonNull android.content.Context, int, int, int);
     method public static android.app.ActivityOptions makeScaleUpAnimation(android.view.View, int, int, int, int);
     method public static android.app.ActivityOptions makeSceneTransitionAnimation(android.app.Activity, android.view.View, String);
     method @java.lang.SafeVarargs public static android.app.ActivityOptions makeSceneTransitionAnimation(android.app.Activity, android.util.Pair<android.view.View,java.lang.String>...);
@@ -12951,6 +12953,10 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.database.CursorWindow> CREATOR;
   }
 
+  public class CursorWindowAllocationException extends java.lang.RuntimeException {
+    ctor public CursorWindowAllocationException(@NonNull String);
+  }
+
   public class CursorWrapper implements android.database.Cursor {
     ctor public CursorWrapper(android.database.Cursor);
     method public void close();
@@ -31681,6 +31687,7 @@
     method public boolean isDeviceLightIdleMode();
     method public boolean isIgnoringBatteryOptimizations(String);
     method public boolean isInteractive();
+    method public boolean isLowPowerStandbyEnabled();
     method public boolean isPowerSaveMode();
     method public boolean isRebootingUserspaceSupported();
     method @Deprecated public boolean isScreenOn();
@@ -31692,6 +31699,7 @@
     field public static final int ACQUIRE_CAUSES_WAKEUP = 268435456; // 0x10000000
     field public static final String ACTION_DEVICE_IDLE_MODE_CHANGED = "android.os.action.DEVICE_IDLE_MODE_CHANGED";
     field public static final String ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED = "android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGED";
+    field public static final String ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED = "android.os.action.LOW_POWER_STANDBY_ENABLED_CHANGED";
     field public static final String ACTION_POWER_SAVE_MODE_CHANGED = "android.os.action.POWER_SAVE_MODE_CHANGED";
     field @Deprecated public static final int FULL_WAKE_LOCK = 26; // 0x1a
     field public static final int LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF = 2; // 0x2
@@ -32416,6 +32424,7 @@
     method @Nullable public android.os.storage.StorageVolume getStorageVolume(@NonNull java.io.File);
     method @NonNull public android.os.storage.StorageVolume getStorageVolume(@NonNull android.net.Uri);
     method @NonNull public java.util.List<android.os.storage.StorageVolume> getStorageVolumes();
+    method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE) public java.util.List<android.os.storage.StorageVolume> getStorageVolumesIncludingSharedProfiles();
     method @NonNull public java.util.UUID getUuidForPath(@NonNull java.io.File) throws java.io.IOException;
     method public boolean isAllocationSupported(@NonNull java.io.FileDescriptor);
     method public boolean isCacheBehaviorGroup(java.io.File) throws java.io.IOException;
@@ -32449,6 +32458,7 @@
     method public String getDescription(android.content.Context);
     method @Nullable public java.io.File getDirectory();
     method @Nullable public String getMediaStoreVolumeName();
+    method @NonNull public android.os.UserHandle getOwner();
     method public String getState();
     method @Nullable public java.util.UUID getStorageUuid();
     method @Nullable public String getUuid();
@@ -37876,6 +37886,7 @@
     method public abstract void onFillRequest(@NonNull android.service.autofill.FillRequest, @NonNull android.os.CancellationSignal, @NonNull android.service.autofill.FillCallback);
     method public abstract void onSaveRequest(@NonNull android.service.autofill.SaveRequest, @NonNull android.service.autofill.SaveCallback);
     method public void onSavedDatasetsInfoRequest(@NonNull android.service.autofill.SavedDatasetsInfoCallback);
+    field public static final String EXTRA_FILL_RESPONSE = "android.service.autofill.extra.FILL_RESPONSE";
     field public static final String SERVICE_INTERFACE = "android.service.autofill.AutofillService";
     field public static final String SERVICE_META_DATA = "android.autofill";
   }
@@ -37926,21 +37937,23 @@
   }
 
   public static final class Dataset.Builder {
-    ctor public Dataset.Builder(@NonNull android.widget.RemoteViews);
+    ctor @Deprecated public Dataset.Builder(@NonNull android.widget.RemoteViews);
+    ctor public Dataset.Builder(@NonNull android.service.autofill.Presentations);
     ctor public Dataset.Builder();
     method @NonNull public android.service.autofill.Dataset build();
     method @NonNull public android.service.autofill.Dataset.Builder setAuthentication(@Nullable android.content.IntentSender);
+    method @NonNull public android.service.autofill.Dataset.Builder setField(@NonNull android.view.autofill.AutofillId, @Nullable android.service.autofill.Field);
     method @NonNull public android.service.autofill.Dataset.Builder setId(@Nullable String);
-    method @NonNull public android.service.autofill.Dataset.Builder setInlinePresentation(@NonNull android.service.autofill.InlinePresentation);
-    method @NonNull public android.service.autofill.Dataset.Builder setInlinePresentation(@NonNull android.service.autofill.InlinePresentation, @NonNull android.service.autofill.InlinePresentation);
-    method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue);
-    method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @NonNull android.widget.RemoteViews);
-    method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern);
-    method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.widget.RemoteViews);
-    method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation);
-    method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation, @NonNull android.service.autofill.InlinePresentation);
-    method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation);
-    method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation, @NonNull android.service.autofill.InlinePresentation);
+    method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setInlinePresentation(@NonNull android.service.autofill.InlinePresentation);
+    method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setInlinePresentation(@NonNull android.service.autofill.InlinePresentation, @NonNull android.service.autofill.InlinePresentation);
+    method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue);
+    method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @NonNull android.widget.RemoteViews);
+    method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern);
+    method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.widget.RemoteViews);
+    method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation);
+    method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation, @NonNull android.service.autofill.InlinePresentation);
+    method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation);
+    method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation, @NonNull android.service.autofill.InlinePresentation);
   }
 
   public final class DateTransformation implements android.os.Parcelable android.service.autofill.Transformation {
@@ -37957,6 +37970,19 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.service.autofill.DateValueSanitizer> CREATOR;
   }
 
+  public final class Field {
+    method @Nullable public android.service.autofill.Presentations getPresentations();
+    method @Nullable public android.view.autofill.AutofillValue getValue();
+  }
+
+  public static final class Field.Builder {
+    ctor public Field.Builder();
+    method @NonNull public android.service.autofill.Field build();
+    method @NonNull public android.service.autofill.Field.Builder setFilter(@Nullable java.util.regex.Pattern);
+    method @NonNull public android.service.autofill.Field.Builder setPresentations(@NonNull android.service.autofill.Presentations);
+    method @NonNull public android.service.autofill.Field.Builder setValue(@NonNull android.view.autofill.AutofillValue);
+  }
+
   public final class FieldClassification {
     method @NonNull public java.util.List<android.service.autofill.FieldClassification.Match> getMatches();
   }
@@ -38016,6 +38042,7 @@
   public final class FillRequest implements android.os.Parcelable {
     method public int describeContents();
     method @Nullable public android.os.Bundle getClientState();
+    method @Nullable public android.content.IntentSender getDelayedFillIntentSender();
     method @NonNull public java.util.List<android.service.autofill.FillContext> getFillContexts();
     method public int getFlags();
     method public int getId();
@@ -38030,6 +38057,7 @@
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.service.autofill.FillResponse> CREATOR;
+    field public static final int FLAG_DELAY_FILL = 4; // 0x4
     field public static final int FLAG_DISABLE_ACTIVITY_ONLY = 2; // 0x2
     field public static final int FLAG_TRACK_CONTEXT_COMMITED = 1; // 0x1
   }
@@ -38039,11 +38067,14 @@
     method @NonNull public android.service.autofill.FillResponse.Builder addDataset(@Nullable android.service.autofill.Dataset);
     method @NonNull public android.service.autofill.FillResponse build();
     method @NonNull public android.service.autofill.FillResponse.Builder disableAutofill(long);
-    method @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews);
-    method @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews, @Nullable android.service.autofill.InlinePresentation);
-    method @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews, @Nullable android.service.autofill.InlinePresentation, @Nullable android.service.autofill.InlinePresentation);
+    method @Deprecated @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews);
+    method @Deprecated @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews, @Nullable android.service.autofill.InlinePresentation);
+    method @Deprecated @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews, @Nullable android.service.autofill.InlinePresentation, @Nullable android.service.autofill.InlinePresentation);
+    method @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.service.autofill.Presentations);
     method @NonNull public android.service.autofill.FillResponse.Builder setClientState(@Nullable android.os.Bundle);
+    method @NonNull public android.service.autofill.FillResponse.Builder setDialogHeader(@NonNull android.widget.RemoteViews);
     method @NonNull public android.service.autofill.FillResponse.Builder setFieldClassificationIds(@NonNull android.view.autofill.AutofillId...);
+    method @NonNull public android.service.autofill.FillResponse.Builder setFillDialogTriggerIds(@NonNull android.view.autofill.AutofillId...);
     method @NonNull public android.service.autofill.FillResponse.Builder setFlags(int);
     method @NonNull public android.service.autofill.FillResponse.Builder setFooter(@NonNull android.widget.RemoteViews);
     method @NonNull public android.service.autofill.FillResponse.Builder setHeader(@NonNull android.widget.RemoteViews);
@@ -38088,6 +38119,22 @@
   public interface OnClickAction {
   }
 
+  public final class Presentations {
+    method @Nullable public android.widget.RemoteViews getDialogPresentation();
+    method @Nullable public android.service.autofill.InlinePresentation getInlinePresentation();
+    method @Nullable public android.service.autofill.InlinePresentation getInlineTooltipPresentation();
+    method @Nullable public android.widget.RemoteViews getMenuPresentation();
+  }
+
+  public static final class Presentations.Builder {
+    ctor public Presentations.Builder();
+    method @NonNull public android.service.autofill.Presentations build();
+    method @NonNull public android.service.autofill.Presentations.Builder setDialogPresentation(@NonNull android.widget.RemoteViews);
+    method @NonNull public android.service.autofill.Presentations.Builder setInlinePresentation(@NonNull android.service.autofill.InlinePresentation);
+    method @NonNull public android.service.autofill.Presentations.Builder setInlineTooltipPresentation(@NonNull android.service.autofill.InlinePresentation);
+    method @NonNull public android.service.autofill.Presentations.Builder setMenuPresentation(@NonNull android.widget.RemoteViews);
+  }
+
   public final class RegexValidator implements android.os.Parcelable android.service.autofill.Validator {
     ctor public RegexValidator(@NonNull android.view.autofill.AutofillId, @NonNull java.util.regex.Pattern);
     method public int describeContents();
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 01fb989..df61a96 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -446,6 +446,17 @@
 
 }
 
+package android.net.netstats {
+
+  public class NetworkStatsDataMigrationUtils {
+    method @NonNull public static android.net.NetworkStatsCollection readPlatformCollection(@NonNull String, long) throws java.io.IOException;
+    field public static final String PREFIX_UID = "uid";
+    field public static final String PREFIX_UID_TAG = "uid_tag";
+    field public static final String PREFIX_XT = "xt";
+  }
+
+}
+
 package android.os {
 
   public final class BatteryStatsManager {
@@ -541,10 +552,6 @@
     field public static final int APP_IO_BLOCKED_REASON_UNKNOWN = 0; // 0x0
   }
 
-  public final class StorageVolume implements android.os.Parcelable {
-    method @NonNull public android.os.UserHandle getOwner();
-  }
-
 }
 
 package android.provider {
@@ -559,6 +566,26 @@
     field public static final String NAMESPACE_DEVICE_IDLE = "device_idle";
   }
 
+  public static final class Settings.Global extends android.provider.Settings.NameValueTable {
+    field public static final String BLE_SCAN_ALWAYS_AVAILABLE = "ble_scan_always_enabled";
+    field public static final String BLE_SCAN_BACKGROUND_MODE = "ble_scan_background_mode";
+    field public static final String BLE_SCAN_BALANCED_INTERVAL_MS = "ble_scan_balanced_interval_ms";
+    field public static final String BLE_SCAN_BALANCED_WINDOW_MS = "ble_scan_balanced_window_ms";
+    field public static final String BLE_SCAN_LOW_LATENCY_INTERVAL_MS = "ble_scan_low_latency_interval_ms";
+    field public static final String BLE_SCAN_LOW_LATENCY_WINDOW_MS = "ble_scan_low_latency_window_ms";
+    field public static final String BLE_SCAN_LOW_POWER_INTERVAL_MS = "ble_scan_low_power_interval_ms";
+    field public static final String BLE_SCAN_LOW_POWER_WINDOW_MS = "ble_scan_low_power_window_ms";
+    field public static final String BLUETOOTH_BTSNOOP_DEFAULT_MODE = "bluetooth_btsnoop_default_mode";
+    field public static final String BLUETOOTH_CLASS_OF_DEVICE = "bluetooth_class_of_device";
+    field public static final String BLUETOOTH_DISABLED_PROFILES = "bluetooth_disabled_profiles";
+  }
+
+  public static final class Settings.Secure extends android.provider.Settings.NameValueTable {
+    field public static final String BLUETOOTH_ADDRESS = "bluetooth_address";
+    field public static final String BLUETOOTH_ADDR_VALID = "bluetooth_addr_valid";
+    field public static final String BLUETOOTH_NAME = "bluetooth_name";
+  }
+
 }
 
 package android.telephony {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 0f9c160..6a50670 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -72,6 +72,7 @@
     field public static final String BIND_TELEPHONY_NETWORK_SERVICE = "android.permission.BIND_TELEPHONY_NETWORK_SERVICE";
     field public static final String BIND_TEXTCLASSIFIER_SERVICE = "android.permission.BIND_TEXTCLASSIFIER_SERVICE";
     field public static final String BIND_TIME_ZONE_PROVIDER_SERVICE = "android.permission.BIND_TIME_ZONE_PROVIDER_SERVICE";
+    field public static final String BIND_TRACE_REPORT_SERVICE = "android.permission.BIND_TRACE_REPORT_SERVICE";
     field public static final String BIND_TRANSLATION_SERVICE = "android.permission.BIND_TRANSLATION_SERVICE";
     field public static final String BIND_TRUST_AGENT = "android.permission.BIND_TRUST_AGENT";
     field public static final String BIND_TV_REMOTE_SERVICE = "android.permission.BIND_TV_REMOTE_SERVICE";
@@ -168,6 +169,7 @@
     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";
+    field public static final String MANAGE_LOW_POWER_STANDBY = "android.permission.MANAGE_LOW_POWER_STANDBY";
     field public static final String MANAGE_MUSIC_RECOGNITION = "android.permission.MANAGE_MUSIC_RECOGNITION";
     field public static final String MANAGE_NOTIFICATION_LISTENERS = "android.permission.MANAGE_NOTIFICATION_LISTENERS";
     field public static final String MANAGE_ONE_TIME_PERMISSION_SESSIONS = "android.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS";
@@ -2112,6 +2114,7 @@
     method @Nullable public android.net.Uri getSliceUri();
     method @NonNull public String getSmartspaceTargetId();
     method @Nullable public String getSourceNotificationKey();
+    method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData getTemplateData();
     method @NonNull public android.os.UserHandle getUserHandle();
     method @Nullable public android.appwidget.AppWidgetProviderInfo getWidget();
     method public boolean isSensitive();
@@ -2142,6 +2145,14 @@
     field public static final int FEATURE_UPCOMING_ALARM = 23; // 0x17
     field public static final int FEATURE_WEATHER = 1; // 0x1
     field public static final int FEATURE_WEATHER_ALERT = 10; // 0xa
+    field public static final int UI_TEMPLATE_CAROUSEL = 4; // 0x4
+    field public static final int UI_TEMPLATE_COMBINED_CARDS = 6; // 0x6
+    field public static final int UI_TEMPLATE_DEFAULT = 1; // 0x1
+    field public static final int UI_TEMPLATE_HEAD_TO_HEAD = 5; // 0x5
+    field public static final int UI_TEMPLATE_SUB_CARD = 7; // 0x7
+    field public static final int UI_TEMPLATE_SUB_IMAGE = 2; // 0x2
+    field public static final int UI_TEMPLATE_SUB_LIST = 3; // 0x3
+    field public static final int UI_TEMPLATE_UNDEFINED = 0; // 0x0
   }
 
   public static final class SmartspaceTarget.Builder {
@@ -2160,6 +2171,7 @@
     method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setShouldShowExpanded(boolean);
     method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setSliceUri(@NonNull android.net.Uri);
     method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setSourceNotificationKey(@NonNull String);
+    method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setTemplateData(@Nullable android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData);
     method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setWidget(@NonNull android.appwidget.AppWidgetProviderInfo);
   }
 
@@ -2188,6 +2200,177 @@
 
 }
 
+package android.app.smartspace.uitemplatedata {
+
+  public final class SmartspaceCarouselUiTemplateData extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData {
+    method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceTapAction getCarouselAction();
+    method @NonNull public java.util.List<android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData.CarouselItem> getCarouselItems();
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData> CREATOR;
+  }
+
+  public static final class SmartspaceCarouselUiTemplateData.Builder extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder {
+    ctor public SmartspaceCarouselUiTemplateData.Builder(@NonNull java.util.List<android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData.CarouselItem>);
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData build();
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData.Builder setCarouselAction(@NonNull android.app.smartspace.uitemplatedata.SmartspaceTapAction);
+  }
+
+  public static final class SmartspaceCarouselUiTemplateData.CarouselItem implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceIcon getImage();
+    method @Nullable public CharSequence getLowerText();
+    method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceTapAction getTapAction();
+    method @Nullable public CharSequence getUpperText();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData.CarouselItem> CREATOR;
+  }
+
+  public static final class SmartspaceCarouselUiTemplateData.CarouselItem.Builder {
+    ctor public SmartspaceCarouselUiTemplateData.CarouselItem.Builder();
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData.CarouselItem build();
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData.CarouselItem.Builder setImage(@Nullable android.app.smartspace.uitemplatedata.SmartspaceIcon);
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData.CarouselItem.Builder setLowerText(@Nullable CharSequence);
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData.CarouselItem.Builder setTapAction(@Nullable android.app.smartspace.uitemplatedata.SmartspaceTapAction);
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData.CarouselItem.Builder setUpperText(@Nullable CharSequence);
+  }
+
+  public final class SmartspaceCombinedCardsUiTemplateData extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData {
+    method @NonNull public java.util.List<android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData> getCombinedCardDataList();
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.SmartspaceCombinedCardsUiTemplateData> CREATOR;
+  }
+
+  public static final class SmartspaceCombinedCardsUiTemplateData.Builder extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder {
+    ctor public SmartspaceCombinedCardsUiTemplateData.Builder(@NonNull java.util.List<android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData>);
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceCombinedCardsUiTemplateData build();
+  }
+
+  public class SmartspaceDefaultUiTemplateData implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceTapAction getPrimaryTapAction();
+    method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceIcon getSubTitleIcon();
+    method @Nullable public CharSequence getSubtitleText();
+    method @Nullable public CharSequence getSupplementalAlarmText();
+    method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceIcon getSupplementalSubtitleIcon();
+    method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceTapAction getSupplementalSubtitleTapAction();
+    method @Nullable public CharSequence getSupplementalSubtitleText();
+    method public int getTemplateType();
+    method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceIcon getTitleIcon();
+    method @Nullable public CharSequence getTitleText();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData> CREATOR;
+  }
+
+  public static class SmartspaceDefaultUiTemplateData.Builder {
+    ctor public SmartspaceDefaultUiTemplateData.Builder(int);
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData build();
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder setPrimaryTapAction(@NonNull android.app.smartspace.uitemplatedata.SmartspaceTapAction);
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder setSubTitleIcon(@NonNull android.app.smartspace.uitemplatedata.SmartspaceIcon);
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder setSubtitleText(@NonNull CharSequence);
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder setSupplementalAlarmText(@NonNull CharSequence);
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder setSupplementalSubtitleIcon(@NonNull android.app.smartspace.uitemplatedata.SmartspaceIcon);
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder setSupplementalSubtitleTapAction(@NonNull android.app.smartspace.uitemplatedata.SmartspaceTapAction);
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder setSupplementalSubtitleText(@NonNull CharSequence);
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder setTitleIcon(@NonNull android.app.smartspace.uitemplatedata.SmartspaceIcon);
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder setTitleText(@NonNull CharSequence);
+  }
+
+  public final class SmartspaceHeadToHeadUiTemplateData extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData {
+    method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceTapAction getHeadToHeadAction();
+    method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceIcon getHeadToHeadFirstCompetitorIcon();
+    method @Nullable public CharSequence getHeadToHeadFirstCompetitorText();
+    method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceIcon getHeadToHeadSecondCompetitorIcon();
+    method @Nullable public CharSequence getHeadToHeadSecondCompetitorText();
+    method @Nullable public CharSequence getHeadToHeadTitle();
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.SmartspaceHeadToHeadUiTemplateData> CREATOR;
+  }
+
+  public static final class SmartspaceHeadToHeadUiTemplateData.Builder extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder {
+    ctor public SmartspaceHeadToHeadUiTemplateData.Builder();
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceHeadToHeadUiTemplateData build();
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceHeadToHeadUiTemplateData.Builder setHeadToHeadAction(@Nullable android.app.smartspace.uitemplatedata.SmartspaceTapAction);
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceHeadToHeadUiTemplateData.Builder setHeadToHeadFirstCompetitorIcon(@Nullable android.app.smartspace.uitemplatedata.SmartspaceIcon);
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceHeadToHeadUiTemplateData.Builder setHeadToHeadFirstCompetitorText(@Nullable CharSequence);
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceHeadToHeadUiTemplateData.Builder setHeadToHeadSecondCompetitorIcon(@Nullable android.app.smartspace.uitemplatedata.SmartspaceIcon);
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceHeadToHeadUiTemplateData.Builder setHeadToHeadSecondCompetitorText(@Nullable CharSequence);
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceHeadToHeadUiTemplateData.Builder setHeadToHeadTitle(@Nullable CharSequence);
+  }
+
+  public final class SmartspaceIcon implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public CharSequence getContentDescription();
+    method @NonNull public android.graphics.drawable.Icon getIcon();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.SmartspaceIcon> CREATOR;
+  }
+
+  public static final class SmartspaceIcon.Builder {
+    ctor public SmartspaceIcon.Builder(@NonNull android.graphics.drawable.Icon);
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceIcon build();
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceIcon.Builder setContentDescription(@NonNull CharSequence);
+  }
+
+  public final class SmartspaceSubCardUiTemplateData extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData {
+    method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceTapAction getSubCardAction();
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceIcon getSubCardIcon();
+    method @Nullable public CharSequence getSubCardText();
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.SmartspaceSubCardUiTemplateData> CREATOR;
+  }
+
+  public static final class SmartspaceSubCardUiTemplateData.Builder extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder {
+    ctor public SmartspaceSubCardUiTemplateData.Builder(@NonNull android.app.smartspace.uitemplatedata.SmartspaceIcon);
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceSubCardUiTemplateData build();
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceSubCardUiTemplateData.Builder setSubCardAction(@NonNull CharSequence);
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceSubCardUiTemplateData.Builder setSubCardAction(@NonNull android.app.smartspace.uitemplatedata.SmartspaceTapAction);
+  }
+
+  public final class SmartspaceSubImageUiTemplateData extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData {
+    method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceTapAction getSubImageAction();
+    method @NonNull public java.util.List<java.lang.CharSequence> getSubImageTexts();
+    method @NonNull public java.util.List<android.app.smartspace.uitemplatedata.SmartspaceIcon> getSubImages();
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.SmartspaceSubImageUiTemplateData> CREATOR;
+  }
+
+  public static final class SmartspaceSubImageUiTemplateData.Builder extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder {
+    ctor public SmartspaceSubImageUiTemplateData.Builder(@NonNull java.util.List<java.lang.CharSequence>, @NonNull java.util.List<android.app.smartspace.uitemplatedata.SmartspaceIcon>);
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceSubImageUiTemplateData build();
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceSubImageUiTemplateData.Builder setCarouselAction(@NonNull android.app.smartspace.uitemplatedata.SmartspaceTapAction);
+  }
+
+  public final class SmartspaceSubListUiTemplateData extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData {
+    method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceTapAction getSubListAction();
+    method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceIcon getSubListIcon();
+    method @NonNull public java.util.List<java.lang.CharSequence> getSubListTexts();
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.SmartspaceSubListUiTemplateData> CREATOR;
+  }
+
+  public static final class SmartspaceSubListUiTemplateData.Builder extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder {
+    ctor public SmartspaceSubListUiTemplateData.Builder(@NonNull java.util.List<java.lang.CharSequence>);
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceSubListUiTemplateData build();
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceSubListUiTemplateData.Builder setCarouselAction(@NonNull android.app.smartspace.uitemplatedata.SmartspaceTapAction);
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceSubListUiTemplateData.Builder setSubListIcon(@NonNull android.app.smartspace.uitemplatedata.SmartspaceIcon);
+  }
+
+  public final class SmartspaceTapAction implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public android.os.Bundle getExtras();
+    method @Nullable public CharSequence getId();
+    method @Nullable public android.content.Intent getIntent();
+    method @Nullable public android.app.PendingIntent getPendingIntent();
+    method @Nullable public android.os.UserHandle getUserHandle();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.SmartspaceTapAction> CREATOR;
+  }
+
+  public static final class SmartspaceTapAction.Builder {
+    ctor public SmartspaceTapAction.Builder(@NonNull CharSequence);
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceTapAction build();
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceTapAction.Builder setExtras(@NonNull android.os.Bundle);
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceTapAction.Builder setIntent(@NonNull android.content.Intent);
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceTapAction.Builder setPendingIntent(@NonNull android.app.PendingIntent);
+    method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceTapAction.Builder setUserHandle(@Nullable android.os.UserHandle);
+  }
+
+}
+
 package android.app.time {
 
   public final class Capabilities {
@@ -2357,12 +2540,21 @@
 
   public class AppHibernationManager {
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public java.util.List<java.lang.String> getHibernatingPackagesForUser();
+    method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public java.util.Map<java.lang.String,android.apphibernation.HibernationStats> getHibernationStatsForUser(@NonNull java.util.Set<java.lang.String>);
+    method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public java.util.Map<java.lang.String,android.apphibernation.HibernationStats> getHibernationStatsForUser();
     method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public boolean isHibernatingForUser(@NonNull String);
     method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public boolean isHibernatingGlobally(@NonNull String);
     method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public void setHibernatingForUser(@NonNull String, boolean);
     method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public void setHibernatingGlobally(@NonNull String, boolean);
   }
 
+  public final class HibernationStats implements android.os.Parcelable {
+    method public int describeContents();
+    method public long getDiskBytesSaved();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.apphibernation.HibernationStats> CREATOR;
+  }
+
 }
 
 package android.companion {
@@ -2419,6 +2611,8 @@
 
   public final class VirtualDeviceParams implements android.os.Parcelable {
     method public int describeContents();
+    method @Nullable public java.util.Set<android.content.ComponentName> getAllowedActivities();
+    method @Nullable public java.util.Set<android.content.ComponentName> getBlockedActivities();
     method public int getLockState();
     method @NonNull public java.util.Set<android.os.UserHandle> getUsersWithMatchingAccounts();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -2430,6 +2624,8 @@
   public static final class VirtualDeviceParams.Builder {
     ctor public VirtualDeviceParams.Builder();
     method @NonNull public android.companion.virtual.VirtualDeviceParams build();
+    method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedActivities(@Nullable java.util.Set<android.content.ComponentName>);
+    method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedActivities(@Nullable java.util.Set<android.content.ComponentName>);
     method @NonNull @RequiresPermission(value=android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY, conditional=true) public android.companion.virtual.VirtualDeviceParams.Builder setLockState(int);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setUsersWithMatchingAccounts(@NonNull java.util.Set<android.os.UserHandle>);
   }
@@ -2497,7 +2693,7 @@
     field public static final String BATTERY_STATS_SERVICE = "batterystats";
     field @Deprecated public static final int BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS = 1048576; // 0x100000
     field public static final int BIND_ALLOW_FOREGROUND_SERVICE_STARTS_FROM_BACKGROUND = 262144; // 0x40000
-    field public static final String CLOUDSEARCH_SERVICE = "cloudsearch_service";
+    field public static final String CLOUDSEARCH_SERVICE = "cloudsearch";
     field public static final String CONTENT_SUGGESTIONS_SERVICE = "content_suggestions";
     field public static final String CONTEXTHUB_SERVICE = "contexthub";
     field public static final String ETHERNET_SERVICE = "ethernet";
@@ -6285,6 +6481,7 @@
     method @RequiresPermission(android.Manifest.permission.CAPTURE_TV_INPUT) public boolean captureFrame(String, android.view.Surface, android.media.tv.TvStreamConfig);
     method @NonNull public java.util.List<java.lang.String> getAvailableExtensionInterfaceNames(@NonNull String);
     method @RequiresPermission(android.Manifest.permission.CAPTURE_TV_INPUT) public java.util.List<android.media.tv.TvStreamConfig> getAvailableTvStreamConfigList(String);
+    method @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public int getClientPid(@NonNull String);
     method public int getClientPriority(int, @Nullable String);
     method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TUNED_INFO) public java.util.List<android.media.tv.TunedInfo> getCurrentTunedInfos();
     method @NonNull @RequiresPermission("android.permission.DVB_DEVICE") public java.util.List<android.media.tv.DvbDeviceInfo> getDvbDeviceList();
@@ -8726,6 +8923,8 @@
     method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportBleScanResults(@NonNull android.os.WorkSource, int);
     method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportBleScanStarted(@NonNull android.os.WorkSource, boolean);
     method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportBleScanStopped(@NonNull android.os.WorkSource, boolean);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void reportBluetoothOff(int, int, @NonNull String);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void reportBluetoothOn(int, int, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportFullWifiLockAcquiredFromSource(@NonNull android.os.WorkSource);
     method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportFullWifiLockReleasedFromSource(@NonNull android.os.WorkSource);
     method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportMobileRadioPowerState(boolean, int);
@@ -9123,11 +9322,14 @@
     method @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) public boolean isAmbientDisplayAvailable();
     method @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) public boolean isAmbientDisplaySuppressed();
     method @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) public boolean isAmbientDisplaySuppressedForToken(@NonNull String);
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, android.Manifest.permission.DEVICE_POWER}) public boolean isLowPowerStandbySupported();
     method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setAdaptivePowerSaveEnabled(boolean);
     method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setAdaptivePowerSavePolicy(@NonNull android.os.BatterySaverPolicyConfig);
     method @RequiresPermission(anyOf={android.Manifest.permission.BATTERY_PREDICTION, android.Manifest.permission.DEVICE_POWER}) public void setBatteryDischargePrediction(@NonNull java.time.Duration, boolean);
     method @RequiresPermission(android.Manifest.permission.POWER_SAVER) public boolean setDynamicPowerSaveHint(boolean, int);
     method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setFullPowerSavePolicy(@NonNull android.os.BatterySaverPolicyConfig);
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, android.Manifest.permission.DEVICE_POWER}) public void setLowPowerStandbyActiveDuringMaintenance(boolean);
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, android.Manifest.permission.DEVICE_POWER}) public void setLowPowerStandbyEnabled(boolean);
     method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setPowerSaveModeEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void suppressAmbientDisplay(@NonNull String, boolean);
     method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.USER_ACTIVITY}) public void userActivity(long, int, int);
@@ -10448,9 +10650,9 @@
   }
 
   public static final class Dataset.Builder {
-    ctor public Dataset.Builder(@NonNull android.service.autofill.InlinePresentation);
+    ctor @Deprecated public Dataset.Builder(@NonNull android.service.autofill.InlinePresentation);
     method @NonNull public android.service.autofill.Dataset.Builder setContent(@NonNull android.view.autofill.AutofillId, @Nullable android.content.ClipData);
-    method @NonNull public android.service.autofill.Dataset.Builder setFieldInlinePresentation(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.service.autofill.InlinePresentation);
+    method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setFieldInlinePresentation(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.service.autofill.InlinePresentation);
   }
 
   public abstract class InlineSuggestionRenderService extends android.app.Service {
@@ -11002,7 +11204,7 @@
     method @android.service.persistentdata.PersistentDataBlockManager.FlashLockState @RequiresPermission(anyOf={android.Manifest.permission.READ_OEM_UNLOCK_STATE, "android.permission.OEM_UNLOCK_STATE"}) public int getFlashLockState();
     method public long getMaximumDataBlockSize();
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_OEM_UNLOCK_STATE, "android.permission.OEM_UNLOCK_STATE"}) public boolean getOemUnlockEnabled();
-    method @NonNull public String getPersistentDataPackageName();
+    method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_PDB_STATE) public String getPersistentDataPackageName();
     method public byte[] read();
     method @Deprecated @RequiresPermission("android.permission.OEM_UNLOCK_STATE") public void setOemUnlockEnabled(boolean);
     method @RequiresPermission("android.permission.OEM_UNLOCK_STATE") public void wipe();
@@ -11240,6 +11442,22 @@
 
 }
 
+package android.service.tracing {
+
+  public class TraceReportService extends android.app.Service {
+    ctor public TraceReportService();
+    method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
+    method public boolean onMessage(@NonNull android.os.Message);
+    method public void onReportTrace(@NonNull android.service.tracing.TraceReportService.TraceParams);
+  }
+
+  public static final class TraceReportService.TraceParams {
+    method @NonNull public android.os.ParcelFileDescriptor getFd();
+    method @NonNull public java.util.UUID getUuid();
+  }
+
+}
+
 package android.service.translation {
 
   public abstract class TranslationService extends android.app.Service {
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index dbb4274..e17a9bb 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -294,7 +294,6 @@
 
 
 ServiceName: android.content.Context#CLOUDSEARCH_SERVICE:
-    Inconsistent service value; expected `cloudsearch`, was `cloudsearch_service` (Note: Do not change the name of already released services, which will break tools using `adb shell dumpsys`. Instead add `@SuppressLint("ServiceName"))`
 
 
 UserHandleName: android.app.search.SearchAction.Builder#setUserHandle(android.os.UserHandle):
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 0bdbd10..c39394b 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2,6 +2,7 @@
 package android {
 
   public static final class Manifest.permission {
+    field public static final String ACCESS_KEYGUARD_SECURE_STORAGE = "android.permission.ACCESS_KEYGUARD_SECURE_STORAGE";
     field public static final String ACCESS_NOTIFICATIONS = "android.permission.ACCESS_NOTIFICATIONS";
     field public static final String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING";
     field public static final String APPROVE_INCIDENT_REPORTS = "android.permission.APPROVE_INCIDENT_REPORTS";
@@ -105,6 +106,7 @@
 
   @UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.OnBackInvokedDispatcherOwner android.view.View.OnCreateContextMenuListener android.view.Window.Callback {
     method public final boolean addDumpable(@NonNull android.util.Dumpable);
+    method public void dumpInternal(@NonNull String, @Nullable java.io.FileDescriptor, @NonNull java.io.PrintWriter, @Nullable String[]);
     method public void onMovedToDisplay(int, android.content.res.Configuration);
   }
 
@@ -152,7 +154,7 @@
 
   public class ActivityOptions {
     method @NonNull public static android.app.ActivityOptions fromBundle(@NonNull android.os.Bundle);
-    method @NonNull public static android.app.ActivityOptions makeCustomAnimation(@NonNull android.content.Context, int, int, @Nullable android.os.Handler, @Nullable android.app.ActivityOptions.OnAnimationStartedListener, @Nullable android.app.ActivityOptions.OnAnimationFinishedListener);
+    method @NonNull public static android.app.ActivityOptions makeCustomAnimation(@NonNull android.content.Context, int, int, int, @Nullable android.os.Handler, @Nullable android.app.ActivityOptions.OnAnimationStartedListener, @Nullable android.app.ActivityOptions.OnAnimationFinishedListener);
     method @NonNull @RequiresPermission(android.Manifest.permission.START_TASKS_FROM_RECENTS) public static android.app.ActivityOptions makeCustomTaskAnimation(@NonNull android.content.Context, int, int, @Nullable android.os.Handler, @Nullable android.app.ActivityOptions.OnAnimationStartedListener, @Nullable android.app.ActivityOptions.OnAnimationFinishedListener);
     method public static void setExitTransitionTimeout(long);
     method public void setLaunchActivityType(int);
@@ -286,8 +288,8 @@
   }
 
   public class KeyguardManager {
-    method @RequiresPermission(anyOf={android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS, "android.permission.ACCESS_KEYGUARD_SECURE_STORAGE"}) public boolean checkLock(int, @Nullable byte[]);
-    method @RequiresPermission(anyOf={android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS, "android.permission.ACCESS_KEYGUARD_SECURE_STORAGE"}) public boolean setLock(int, @Nullable byte[], int, @Nullable byte[]);
+    method @RequiresPermission(anyOf={android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS, android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE}) public boolean checkLock(int, @Nullable byte[]);
+    method @RequiresPermission(anyOf={android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS, android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE}) public boolean setLock(int, @Nullable byte[], int, @Nullable byte[]);
   }
 
   public class LocaleManager {
@@ -461,6 +463,7 @@
     method @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceSecurityLogs();
     method public void forceUpdateUserSetupComplete(int);
     method @NonNull public java.util.Set<java.lang.String> getDefaultCrossProfilePackages();
+    method public int getDeviceOwnerType(@NonNull android.content.ComponentName);
     method @NonNull public java.util.Set<java.lang.String> getDisallowedSystemApps(@NonNull android.content.ComponentName, int, @NonNull String);
     method public long getLastBugReportRequestTime();
     method public long getLastNetworkLogRetrievalTime();
@@ -476,6 +479,7 @@
     method @RequiresPermission(allOf={android.Manifest.permission.MANAGE_DEVICE_ADMINS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void setActiveAdmin(@NonNull android.content.ComponentName, boolean, int);
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public boolean setDeviceOwner(@NonNull android.content.ComponentName, @Nullable String, int);
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public boolean setDeviceOwnerOnly(@NonNull android.content.ComponentName, @Nullable String, int);
+    method public void setDeviceOwnerType(@NonNull android.content.ComponentName, int);
     method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public void setNextOperationSafety(int, int);
     field public static final String ACTION_DATA_SHARING_RESTRICTION_APPLIED = "android.app.action.DATA_SHARING_RESTRICTION_APPLIED";
     field public static final String ACTION_DEVICE_POLICY_CONSTANTS_CHANGED = "android.app.action.DEVICE_POLICY_CONSTANTS_CHANGED";
@@ -581,6 +585,15 @@
 
 }
 
+package android.app.trust {
+
+  public class TrustManager {
+    method @RequiresPermission(android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE) public void enableTrustAgentForUserForTest(@NonNull android.content.ComponentName, int);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE) public void reportUserRequestedUnlock(int);
+  }
+
+}
+
 package android.app.usage {
 
   public class NetworkStatsManager {
@@ -1718,7 +1731,9 @@
   }
 
   public final class PowerManager {
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, android.Manifest.permission.DEVICE_POWER}) public void forceLowPowerStandbyActive(boolean);
     field public static final String ACTION_ENHANCED_DISCHARGE_PREDICTION_CHANGED = "android.os.action.ENHANCED_DISCHARGE_PREDICTION_CHANGED";
+    field @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public static final int SYSTEM_WAKELOCK = -2147483648; // 0x80000000
   }
 
   public class Process {
@@ -2359,6 +2374,14 @@
 
 }
 
+package android.service.trust {
+
+  public class TrustAgentService extends android.app.Service {
+    method public void onUserRequestedUnlock();
+  }
+
+}
+
 package android.service.voice {
 
   public class AlwaysOnHotwordDetector implements android.service.voice.HotwordDetector {
diff --git a/core/java/Android.bp b/core/java/Android.bp
index a2259f3..8234f03 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -38,6 +38,11 @@
     srcs: ["android/tracing/ITracingServiceProxy.aidl"],
 }
 
+filegroup {
+    name: "TraceReportParams.aidl",
+    srcs: ["android/tracing/TraceReportParams.aidl"],
+}
+
 // These are subset of framework-core-sources that are needed by the
 // android.test.mock library. The implementation of android.test.mock references
 // private members of various components to allow mocking of classes that cannot
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 93d1e03..530666b 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -33,12 +33,16 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.StyleRes;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.annotation.UiContext;
 import android.app.VoiceInteractor.Request;
 import android.app.admin.DevicePolicyManager;
 import android.app.assist.AssistContent;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentCallbacks2;
 import android.content.ComponentName;
@@ -788,6 +792,16 @@
     private static final int LOG_AM_ON_TOP_RESUMED_GAINED_CALLED = 30064;
     private static final int LOG_AM_ON_TOP_RESUMED_LOST_CALLED = 30065;
 
+    /**
+     * After {@link Build.VERSION_CODES#TIRAMISU},
+     * {@link #dump(String, FileDescriptor, PrintWriter, String[])} is not called if
+     * {@code dumpsys activity} is called with some special arguments.
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+    @VisibleForTesting
+    private static final long DUMP_IGNORES_SPECIAL_ARGS = 149254050L;
+
     private static class ManagedDialog {
         Dialog mDialog;
         Bundle mArgs;
@@ -6131,8 +6145,31 @@
      * the outgoing activity.  Use 0 for no animation.
      */
     public void overridePendingTransition(int enterAnim, int exitAnim) {
-        ActivityClient.getInstance().overridePendingTransition(mToken, getPackageName(),
-                enterAnim, exitAnim);
+        overridePendingTransition(enterAnim, exitAnim, 0);
+    }
+
+    /**
+     * Call immediately after one of the flavors of {@link #startActivity(Intent)}
+     * or {@link #finish} to specify an explicit transition animation to
+     * perform next.
+     *
+     * <p>As of {@link android.os.Build.VERSION_CODES#JELLY_BEAN} an alternative
+     * to using this with starting activities is to supply the desired animation
+     * information through a {@link ActivityOptions} bundle to
+     * {@link #startActivity(Intent, Bundle)} or a related function.  This allows
+     * you to specify a custom animation even when starting an activity from
+     * outside the context of the current top activity.
+     *
+     * @param enterAnim A resource ID of the animation resource to use for
+     * the incoming activity.  Use 0 for no animation.
+     * @param exitAnim A resource ID of the animation resource to use for
+     * the outgoing activity.  Use 0 for no animation.
+     * @param backgroundColor The background color to use for the background during the animation if
+     * the animation requires a background. Set to 0 to not override the default color.
+     */
+    public void overridePendingTransition(int enterAnim, int exitAnim, int backgroundColor) {
+        ActivityClient.getInstance().overridePendingTransition(mToken, getPackageName(), enterAnim,
+                exitAnim, backgroundColor);
     }
 
     /**
@@ -7079,7 +7116,18 @@
 
     /**
      * Print the Activity's state into the given stream.  This gets invoked if
-     * you run "adb shell dumpsys activity &lt;activity_component_name&gt;".
+     * you run <code>adb shell dumpsys activity &lt;activity_component_name&gt;</code>.
+     *
+     * <p>This method won't be called if the app targets
+     * {@link android.os.Build.VERSION_CODES#TIRAMISU} or later if the dump request starts with one
+     * of the following arguments:
+     * <ul>
+     *   <li>--autofill
+     *   <li>--contentcapture
+     *   <li>--translation
+     *   <li>--list-dumpables
+     *   <li>--dump-dumpable
+     * </ul>
      *
      * @param prefix Desired prefix to prepend at each line of output.
      * @param fd The raw file descriptor that the dump is being sent to.
@@ -7106,11 +7154,20 @@
         return mDumpableContainer.addDumpable(dumpable);
     }
 
-    void dumpInner(@NonNull String prefix, @Nullable FileDescriptor fd,
+    /**
+     * This is the real method called by {@code ActivityThread}, but it's also exposed so
+     * CTS can test for the special args cases.
+     *
+     * @hide
+     */
+    @TestApi
+    @VisibleForTesting
+    @SuppressLint("OnNameExpected")
+    public void dumpInternal(@NonNull String prefix,
+            @SuppressLint("UseParcelFileDescriptor") @Nullable FileDescriptor fd,
             @NonNull PrintWriter writer, @Nullable String[] args) {
-        String innerPrefix = prefix + "  ";
-
-        if (args != null && args.length > 0) {
+        if (args != null && args.length > 0
+                && CompatChanges.isChangeEnabled(DUMP_IGNORES_SPECIAL_ARGS)) {
             // Handle special cases
             switch (args[0]) {
                 case "--autofill":
@@ -7145,6 +7202,12 @@
                     return;
             }
         }
+        dump(prefix, fd, writer, args);
+    }
+
+    void dumpInner(@NonNull String prefix, @Nullable FileDescriptor fd,
+            @NonNull PrintWriter writer, @Nullable String[] args) {
+        String innerPrefix = prefix + "  ";
 
         writer.print(prefix); writer.print("Local Activity ");
                 writer.print(Integer.toHexString(System.identityHashCode(this)));
diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java
index eb4a355..605a1fa 100644
--- a/core/java/android/app/ActivityClient.java
+++ b/core/java/android/app/ActivityClient.java
@@ -428,11 +428,11 @@
         }
     }
 
-    void overridePendingTransition(IBinder token, String packageName,
-            int enterAnim, int exitAnim) {
+    void overridePendingTransition(IBinder token, String packageName, int enterAnim, int exitAnim,
+            int backgroundColor) {
         try {
             getActivityClientController().overridePendingTransition(token, packageName,
-                    enterAnim, exitAnim);
+                    enterAnim, exitAnim, backgroundColor);
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 5e5649f..e405b60 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -126,6 +126,12 @@
     public static final String KEY_ANIM_IN_PLACE_RES_ID = "android:activity.animInPlaceRes";
 
     /**
+     * Custom background color for animation.
+     * @hide
+     */
+    public static final String KEY_ANIM_BACKGROUND_COLOR = "android:activity.backgroundColor";
+
+    /**
      * Bitmap for thumbnail animation.
      * @hide
      */
@@ -389,6 +395,7 @@
     private int mCustomEnterResId;
     private int mCustomExitResId;
     private int mCustomInPlaceResId;
+    private int mCustomBackgroundColor;
     private Bitmap mThumbnail;
     private int mStartX;
     private int mStartY;
@@ -453,7 +460,27 @@
      */
     public static ActivityOptions makeCustomAnimation(Context context,
             int enterResId, int exitResId) {
-        return makeCustomAnimation(context, enterResId, exitResId, null, null, null);
+        return makeCustomAnimation(context, enterResId, exitResId, 0, null, null);
+    }
+
+    /**
+     * Create an ActivityOptions specifying a custom animation to run when
+     * the activity is displayed.
+     *
+     * @param context Who is defining this.  This is the application that the
+     * animation resources will be loaded from.
+     * @param enterResId A resource ID of the animation resource to use for
+     * the incoming activity.  Use 0 for no animation.
+     * @param exitResId A resource ID of the animation resource to use for
+     * the outgoing activity.  Use 0 for no animation.
+     * @param backgroundColor The background color to use for the background during the animation if
+     * the animation requires a background. Set to 0 to not override the default color.
+     * @return Returns a new ActivityOptions object that you can use to
+     * supply these options as the options Bundle when starting an activity.
+     */
+    public static @NonNull ActivityOptions makeCustomAnimation(@NonNull Context context,
+            int enterResId, int exitResId, int backgroundColor) {
+        return makeCustomAnimation(context, enterResId, exitResId, backgroundColor, null, null);
     }
 
     /**
@@ -477,12 +504,14 @@
      */
     @UnsupportedAppUsage
     public static ActivityOptions makeCustomAnimation(Context context,
-            int enterResId, int exitResId, Handler handler, OnAnimationStartedListener listener) {
+            int enterResId, int exitResId, int backgroundColor, Handler handler,
+            OnAnimationStartedListener listener) {
         ActivityOptions opts = new ActivityOptions();
         opts.mPackageName = context.getPackageName();
         opts.mAnimationType = ANIM_CUSTOM;
         opts.mCustomEnterResId = enterResId;
         opts.mCustomExitResId = exitResId;
+        opts.mCustomBackgroundColor = backgroundColor;
         opts.setOnAnimationStartedListener(handler, listener);
         return opts;
     }
@@ -510,11 +539,11 @@
      */
     @TestApi
     public static @NonNull ActivityOptions makeCustomAnimation(@NonNull Context context,
-            int enterResId, int exitResId, @Nullable Handler handler,
+            int enterResId, int exitResId, int backgroundColor, @Nullable Handler handler,
             @Nullable OnAnimationStartedListener startedListener,
             @Nullable OnAnimationFinishedListener finishedListener) {
-        ActivityOptions opts = makeCustomAnimation(context, enterResId, exitResId, handler,
-                startedListener);
+        ActivityOptions opts = makeCustomAnimation(context, enterResId, exitResId, backgroundColor,
+                handler, startedListener);
         opts.setOnAnimationFinishedListener(handler, finishedListener);
         return opts;
     }
@@ -547,8 +576,8 @@
             int enterResId, int exitResId, @Nullable Handler handler,
             @Nullable OnAnimationStartedListener startedListener,
             @Nullable OnAnimationFinishedListener finishedListener) {
-        ActivityOptions opts = makeCustomAnimation(context, enterResId, exitResId, handler,
-                startedListener, finishedListener);
+        ActivityOptions opts = makeCustomAnimation(context, enterResId, exitResId, 0,
+                handler, startedListener, finishedListener);
         opts.mOverrideTaskTransition = true;
         return opts;
     }
@@ -1243,6 +1272,11 @@
         return mCustomInPlaceResId;
     }
 
+    /** @hide */
+    public int getCustomBackgroundColor() {
+        return mCustomBackgroundColor;
+    }
+
     /**
      * The thumbnail is copied into a hardware bitmap when it is bundled and sent to the system, so
      * it should always be backed by a HardwareBuffer on the other end.
@@ -1775,6 +1809,7 @@
             case ANIM_CUSTOM:
                 mCustomEnterResId = otherOptions.mCustomEnterResId;
                 mCustomExitResId = otherOptions.mCustomExitResId;
+                mCustomBackgroundColor = otherOptions.mCustomBackgroundColor;
                 mThumbnail = null;
                 if (mAnimationStartedListener != null) {
                     try {
@@ -1862,6 +1897,7 @@
             case ANIM_CUSTOM:
                 b.putInt(KEY_ANIM_ENTER_RES_ID, mCustomEnterResId);
                 b.putInt(KEY_ANIM_EXIT_RES_ID, mCustomExitResId);
+                b.putInt(KEY_ANIM_BACKGROUND_COLOR, mCustomBackgroundColor);
                 b.putBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener
                         != null ? mAnimationStartedListener.asBinder() : null);
                 break;
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 3825d8d..876e401 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -200,6 +200,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.content.ReferrerIntent;
+import com.android.internal.os.BinderCallsStats;
 import com.android.internal.os.BinderInternal;
 import com.android.internal.os.RuntimeInit;
 import com.android.internal.os.SomeArgs;
@@ -4591,7 +4592,7 @@
             if (r != null && r.activity != null) {
                 PrintWriter pw = new FastPrintWriter(new FileOutputStream(
                         info.fd.getFileDescriptor()));
-                r.activity.dump(info.prefix, info.fd.getFileDescriptor(), pw, info.args);
+                r.activity.dumpInternal(info.prefix, info.fd.getFileDescriptor(), pw, info.args);
                 pw.flush();
             }
         } finally {
@@ -7812,6 +7813,8 @@
         MediaFrameworkPlatformInitializer.setMediaServiceManager(new MediaServiceManager());
         MediaFrameworkInitializer.setMediaServiceManager(new MediaServiceManager());
         BluetoothFrameworkInitializer.setBluetoothServiceManager(new BluetoothServiceManager());
+        BluetoothFrameworkInitializer.setBinderCallsStatsInitializer(context -> {
+            BinderCallsStats.startForBluetooth(context); });
     }
 
     private void purgePendingResources() {
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 0081234..0d1bc05 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1327,9 +1327,17 @@
      */
     public static final int OP_ESTABLISH_VPN_MANAGER = AppProtoEnums.APP_OP_ESTABLISH_VPN_MANAGER;
 
+    /**
+     * Access restricted settings.
+     *
+     * @hide
+     */
+    public static final int OP_ACCESS_RESTRICTED_SETTINGS =
+            AppProtoEnums.APP_OP_ACCESS_RESTRICTED_SETTINGS;
+
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public static final int _NUM_OP = 119;
+    public static final int _NUM_OP = 120;
 
     /** Access to coarse location information. */
     public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1784,6 +1792,14 @@
     @SystemApi
     public static final String OPSTR_ESTABLISH_VPN_MANAGER = "android:establish_vpn_manager";
 
+    /**
+     * Limit user accessing restricted settings.
+     *
+     * @hide
+     */
+    public static final String OPSTR_ACCESS_RESTRICTED_SETTINGS =
+            "android:access_restricted_settings";
+
     /** {@link #sAppOpsToNote} not initialized yet for this op */
     private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
     /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -2004,6 +2020,7 @@
             OP_NEARBY_WIFI_DEVICES,             // OP_NEARBY_WIFI_DEVICES
             OP_ESTABLISH_VPN_SERVICE,           // OP_ESTABLISH_VPN_SERVICE
             OP_ESTABLISH_VPN_MANAGER,           // OP_ESTABLISH_VPN_MANAGER
+            OP_ACCESS_RESTRICTED_SETTINGS,      // OP_ACCESS_RESTRICTED_SETTINGS
     };
 
     /**
@@ -2129,6 +2146,7 @@
             OPSTR_NEARBY_WIFI_DEVICES,
             OPSTR_ESTABLISH_VPN_SERVICE,
             OPSTR_ESTABLISH_VPN_MANAGER,
+            OPSTR_ACCESS_RESTRICTED_SETTINGS,
     };
 
     /**
@@ -2255,6 +2273,7 @@
             "NEARBY_WIFI_DEVICES",
             "ESTABLISH_VPN_SERVICE",
             "ESTABLISH_VPN_MANAGER",
+            "ACCESS_RESTRICTED_SETTINGS",
     };
 
     /**
@@ -2382,6 +2401,7 @@
             Manifest.permission.NEARBY_WIFI_DEVICES,
             null, // no permission for OP_ESTABLISH_VPN_SERVICE
             null, // no permission for OP_ESTABLISH_VPN_MANAGER
+            null, // no permission for OP_ACCESS_RESTRICTED_SETTINGS,
     };
 
     /**
@@ -2509,6 +2529,7 @@
             null, // NEARBY_WIFI_DEVICES
             null, // ESTABLISH_VPN_SERVICE
             null, // ESTABLISH_VPN_MANAGER
+            null, // ACCESS_RESTRICTED_SETTINGS,
     };
 
     /**
@@ -2635,6 +2656,7 @@
             null, // NEARBY_WIFI_DEVICES
             null, // ESTABLISH_VPN_SERVICE
             null, // ESTABLISH_VPN_MANAGER
+            null, // ACCESS_RESTRICTED_SETTINGS,
     };
 
     /**
@@ -2760,6 +2782,7 @@
             AppOpsManager.MODE_ALLOWED, // NEARBY_WIFI_DEVICES
             AppOpsManager.MODE_ALLOWED, // ESTABLISH_VPN_SERVICE
             AppOpsManager.MODE_ALLOWED, // ESTABLISH_VPN_MANAGER
+            AppOpsManager.MODE_ALLOWED, // ACCESS_RESTRICTED_SETTINGS,
     };
 
     /**
@@ -2889,6 +2912,7 @@
             false, // NEARBY_WIFI_DEVICES
             false, // OP_ESTABLISH_VPN_SERVICE
             false, // OP_ESTABLISH_VPN_MANAGER
+            true, // ACCESS_RESTRICTED_SETTINGS
     };
 
     /**
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index 83c57c5..396e552 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -112,7 +112,7 @@
      * calls, so this method should be the same as them to keep the invocation order.
      */
     void overridePendingTransition(in IBinder token, in String packageName,
-            int enterAnim, int exitAnim);
+            int enterAnim, int exitAnim, int backgroundColor);
     int setVrMode(in IBinder token, boolean enabled, in ComponentName packageName);
 
     /** See {@link android.app.Activity#setDisablePreviewScreenshots}. */
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index dd832c8..bbdd705 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -1034,15 +1034,14 @@
             }});
 
         registerService(Context.PERSISTENT_DATA_BLOCK_SERVICE, PersistentDataBlockManager.class,
-                new CachedServiceFetcher<PersistentDataBlockManager>() {
+                new StaticServiceFetcher<PersistentDataBlockManager>() {
             @Override
-            public PersistentDataBlockManager createService(ContextImpl ctx)
-                    throws ServiceNotFoundException {
+            public PersistentDataBlockManager createService() throws ServiceNotFoundException {
                 IBinder b = ServiceManager.getServiceOrThrow(Context.PERSISTENT_DATA_BLOCK_SERVICE);
                 IPersistentDataBlockService persistentDataBlockService =
                         IPersistentDataBlockService.Stub.asInterface(b);
                 if (persistentDataBlockService != null) {
-                    return new PersistentDataBlockManager(ctx, persistentDataBlockService);
+                    return new PersistentDataBlockManager(persistentDataBlockService);
                 } else {
                     // not supported
                     return null;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 3960f4e..9ac4030 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -454,6 +454,52 @@
      * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li>
      * </ul>
      *
+     * <p>Once the device admin app is set as the device owner, the following APIs are available for
+     * managing polices on the device:
+     * <ul>
+     * <li>{@link #isDeviceManaged()}</li>
+     * <li>{@link #isUninstallBlocked(ComponentName, String)}</li>
+     * <li>{@link #setUninstallBlocked(ComponentName, String, boolean)}</li>
+     * <li>{@link #setUserControlDisabledPackages(ComponentName, List)}</li>
+     * <li>{@link #getUserControlDisabledPackages(ComponentName)}</li>
+     * <li>{@link #setOrganizationName(ComponentName, CharSequence)}</li>
+     * <li>{@link #setShortSupportMessage(ComponentName, CharSequence)}</li>
+     * <li>{@link #isBackupServiceEnabled(ComponentName)}</li>
+     * <li>{@link #setBackupServiceEnabled(ComponentName, boolean)}</li>
+     * <li>{@link #isLockTaskPermitted(String)}</li>
+     * <li>{@link #setLockTaskFeatures(ComponentName, int)}, where the following lock task features
+     * can be set (otherwise a {@link SecurityException} will be thrown):</li>
+     * <ul>
+     *     <li>{@link #LOCK_TASK_FEATURE_SYSTEM_INFO}</li>
+     *     <li>{@link #LOCK_TASK_FEATURE_KEYGUARD}</li>
+     *     <li>{@link #LOCK_TASK_FEATURE_HOME}</li>
+     *     <li>{@link #LOCK_TASK_FEATURE_GLOBAL_ACTIONS}</li>
+     *     <li>{@link #LOCK_TASK_FEATURE_NOTIFICATIONS}</li>
+     * </ul>
+     * <li>{@link #setLockTaskPackages(ComponentName, String[])}</li>
+     * <li>{@link #addPersistentPreferredActivity(ComponentName, IntentFilter, ComponentName)}</li>
+     * <li>{@link #clearPackagePersistentPreferredActivities(ComponentName, String)} </li>
+     * <li>{@link #wipeData(int)}</li>
+     * <li>{@link #isDeviceOwnerApp(String)}</li>
+     * <li>{@link #clearDeviceOwnerApp(String)}</li>
+     * <li>{@link #setPermissionGrantState(ComponentName, String, String, int)}, where
+     * {@link permission#READ_PHONE_STATE} is the <b>only</b> permission that can be
+     * {@link #PERMISSION_GRANT_STATE_GRANTED}, {@link #PERMISSION_GRANT_STATE_DENIED}, or
+     * {@link #PERMISSION_GRANT_STATE_DEFAULT} and can <b>only</b> be applied to the device admin
+     * app (otherwise a {@link SecurityException} will be thrown)</li>
+     * <li>{@link #addUserRestriction(ComponentName, String)}, where the following user restrictions
+     * are permitted (otherwise a {@link SecurityException} will be thrown):</li>
+     * <ul>
+     *     <li>{@link UserManager#DISALLOW_ADD_USER}</li>
+     *     <li>{@link UserManager#DISALLOW_DEBUGGING_FEATURES}</li>
+     *     <li>{@link UserManager#DISALLOW_INSTALL_UNKNOWN_SOURCES}</li>
+     *     <li>{@link UserManager#DISALLOW_SAFE_BOOT}</li>
+     *     <li>{@link UserManager#DISALLOW_CONFIG_DATE_TIME}</li>
+     *     <li>{@link UserManager#DISALLOW_OUTGOING_CALLS}</li>
+     * </ul>
+     * <li>{@link #clearUserRestriction(ComponentName, String)}</li>
+     * </ul>
+     *
      * @hide
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
@@ -14577,6 +14623,7 @@
      *
      * @hide
      */
+    @TestApi
     public void setDeviceOwnerType(@NonNull ComponentName admin,
             @DeviceOwnerType int deviceOwnerType) {
         throwIfParentInstance("setDeviceOwnerType");
@@ -14600,6 +14647,7 @@
      *
      * @hide
      */
+    @TestApi
     @DeviceOwnerType
     public int getDeviceOwnerType(@NonNull ComponentName admin) {
         throwIfParentInstance("getDeviceOwnerType");
diff --git a/core/java/android/app/smartspace/SmartspaceTarget.java b/core/java/android/app/smartspace/SmartspaceTarget.java
index 8e98535..78f51be 100644
--- a/core/java/android/app/smartspace/SmartspaceTarget.java
+++ b/core/java/android/app/smartspace/SmartspaceTarget.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.ComponentName;
 import android.net.Uri;
@@ -131,6 +132,9 @@
     @Nullable
     private final AppWidgetProviderInfo mWidget;
 
+    @Nullable
+    private final SmartspaceDefaultUiTemplateData mTemplateData;
+
     public static final int FEATURE_UNDEFINED = 0;
     public static final int FEATURE_WEATHER = 1;
     public static final int FEATURE_CALENDAR = 2;
@@ -189,6 +193,32 @@
     public @interface FeatureType {
     }
 
+    public static final int UI_TEMPLATE_UNDEFINED = 0;
+    public static final int UI_TEMPLATE_DEFAULT = 1;
+    public static final int UI_TEMPLATE_SUB_IMAGE = 2;
+    public static final int UI_TEMPLATE_SUB_LIST = 3;
+    public static final int UI_TEMPLATE_CAROUSEL = 4;
+    public static final int UI_TEMPLATE_HEAD_TO_HEAD = 5;
+    public static final int UI_TEMPLATE_COMBINED_CARDS = 6;
+    public static final int UI_TEMPLATE_SUB_CARD = 7;
+
+    /**
+     * @hide
+     */
+    @IntDef(prefix = {"UI_TEMPLATE_"}, value = {
+            UI_TEMPLATE_UNDEFINED,
+            UI_TEMPLATE_DEFAULT,
+            UI_TEMPLATE_SUB_IMAGE,
+            UI_TEMPLATE_SUB_LIST,
+            UI_TEMPLATE_CAROUSEL,
+            UI_TEMPLATE_HEAD_TO_HEAD,
+            UI_TEMPLATE_COMBINED_CARDS,
+            UI_TEMPLATE_SUB_CARD
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface UiTemplateType {
+    }
+
     private SmartspaceTarget(Parcel in) {
         this.mSmartspaceTargetId = in.readString();
         this.mHeaderAction = in.readTypedObject(SmartspaceAction.CREATOR);
@@ -207,6 +237,7 @@
         this.mAssociatedSmartspaceTargetId = in.readString();
         this.mSliceUri = in.readTypedObject(Uri.CREATOR);
         this.mWidget = in.readTypedObject(AppWidgetProviderInfo.CREATOR);
+        this.mTemplateData = in.readTypedObject(SmartspaceDefaultUiTemplateData.CREATOR);
     }
 
     private SmartspaceTarget(String smartspaceTargetId,
@@ -217,7 +248,7 @@
             boolean shouldShowExpanded, String sourceNotificationKey,
             ComponentName componentName, UserHandle userHandle,
             String associatedSmartspaceTargetId, Uri sliceUri,
-            AppWidgetProviderInfo widget) {
+            AppWidgetProviderInfo widget, SmartspaceDefaultUiTemplateData templateData) {
         mSmartspaceTargetId = smartspaceTargetId;
         mHeaderAction = headerAction;
         mBaseAction = baseAction;
@@ -235,6 +266,7 @@
         mAssociatedSmartspaceTargetId = associatedSmartspaceTargetId;
         mSliceUri = sliceUri;
         mWidget = widget;
+        mTemplateData = templateData;
     }
 
     /**
@@ -371,6 +403,14 @@
     }
 
     /**
+     * Returns the UI template data.
+     */
+    @Nullable
+    public SmartspaceDefaultUiTemplateData getTemplateData() {
+        return mTemplateData;
+    }
+
+    /**
      * @see Parcelable.Creator
      */
     @NonNull
@@ -405,6 +445,7 @@
         dest.writeString(this.mAssociatedSmartspaceTargetId);
         dest.writeTypedObject(this.mSliceUri, flags);
         dest.writeTypedObject(this.mWidget, flags);
+        dest.writeTypedObject(this.mTemplateData, flags);
     }
 
     @Override
@@ -432,6 +473,7 @@
                 + ", mAssociatedSmartspaceTargetId='" + mAssociatedSmartspaceTargetId + '\''
                 + ", mSliceUri=" + mSliceUri
                 + ", mWidget=" + mWidget
+                + ", mTemplateData=" + mTemplateData
                 + '}';
     }
 
@@ -457,7 +499,8 @@
                 && Objects.equals(mAssociatedSmartspaceTargetId,
                 that.mAssociatedSmartspaceTargetId)
                 && Objects.equals(mSliceUri, that.mSliceUri)
-                && Objects.equals(mWidget, that.mWidget);
+                && Objects.equals(mWidget, that.mWidget)
+                && Objects.equals(mTemplateData, that.mTemplateData);
     }
 
     @Override
@@ -465,7 +508,7 @@
         return Objects.hash(mSmartspaceTargetId, mHeaderAction, mBaseAction, mCreationTimeMillis,
                 mExpiryTimeMillis, mScore, mActionChips, mIconGrid, mFeatureType, mSensitive,
                 mShouldShowExpanded, mSourceNotificationKey, mComponentName, mUserHandle,
-                mAssociatedSmartspaceTargetId, mSliceUri, mWidget);
+                mAssociatedSmartspaceTargetId, mSliceUri, mWidget, mTemplateData);
     }
 
     /**
@@ -476,6 +519,9 @@
     @SystemApi
     public static final class Builder {
         private final String mSmartspaceTargetId;
+        private final ComponentName mComponentName;
+        private final UserHandle mUserHandle;
+
         private SmartspaceAction mHeaderAction;
         private SmartspaceAction mBaseAction;
         private long mCreationTimeMillis;
@@ -487,11 +533,10 @@
         private boolean mSensitive;
         private boolean mShouldShowExpanded;
         private String mSourceNotificationKey;
-        private final ComponentName mComponentName;
-        private final UserHandle mUserHandle;
         private String mAssociatedSmartspaceTargetId;
         private Uri mSliceUri;
         private AppWidgetProviderInfo mWidget;
+        private SmartspaceDefaultUiTemplateData mTemplateData;
 
         /**
          * A builder for {@link SmartspaceTarget}.
@@ -640,6 +685,16 @@
         }
 
         /**
+         * Sets the UI template data.
+         */
+        @NonNull
+        public Builder setTemplateData(
+                @Nullable SmartspaceDefaultUiTemplateData templateData) {
+            mTemplateData = templateData;
+            return this;
+        }
+
+        /**
          * Builds a new {@link SmartspaceTarget}.
          *
          * @throws IllegalStateException when non null fields are set as null.
@@ -655,7 +710,7 @@
                     mHeaderAction, mBaseAction, mCreationTimeMillis, mExpiryTimeMillis, mScore,
                     mActionChips, mIconGrid, mFeatureType, mSensitive, mShouldShowExpanded,
                     mSourceNotificationKey, mComponentName, mUserHandle,
-                    mAssociatedSmartspaceTargetId, mSliceUri, mWidget);
+                    mAssociatedSmartspaceTargetId, mSliceUri, mWidget, mTemplateData);
         }
     }
 }
diff --git a/core/java/android/app/smartspace/SmartspaceUtils.java b/core/java/android/app/smartspace/SmartspaceUtils.java
new file mode 100644
index 0000000..f058ffa
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceUtils.java
@@ -0,0 +1,37 @@
+/*
+ * 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.app.smartspace;
+
+import android.annotation.Nullable;
+
+/**
+ * Utilities for Smartspace data.
+ *
+ * @hide
+ */
+public final class SmartspaceUtils {
+
+    private SmartspaceUtils() {
+    }
+
+    /** Returns true if the passed-in {@link CharSequence}s are equal. */
+    public static boolean isEqual(@Nullable CharSequence cs1, @Nullable CharSequence cs2) {
+        if ((cs1 == null && cs2 != null) || (cs1 != null && cs2 == null)) return false;
+        if (cs1 == null && cs2 == null) return true;
+        return cs1.toString().contentEquals(cs2);
+    }
+}
diff --git a/core/java/android/app/smartspace/uitemplatedata/SmartspaceCarouselUiTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/SmartspaceCarouselUiTemplateData.java
new file mode 100644
index 0000000..c4c4fde
--- /dev/null
+++ b/core/java/android/app/smartspace/uitemplatedata/SmartspaceCarouselUiTemplateData.java
@@ -0,0 +1,360 @@
+/*
+ * 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.app.smartspace.uitemplatedata;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.smartspace.SmartspaceTarget;
+import android.app.smartspace.SmartspaceUtils;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Holds all the relevant data needed to render a Smartspace card with the carousel Ui Template.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceCarouselUiTemplateData extends SmartspaceDefaultUiTemplateData {
+
+    /** Lists of {@link CarouselItem}. */
+    @NonNull
+    private final List<CarouselItem> mCarouselItems;
+
+    /** Tap action for the entire carousel secondary card, including the blank space */
+    @Nullable
+    private final SmartspaceTapAction mCarouselAction;
+
+    SmartspaceCarouselUiTemplateData(@NonNull Parcel in) {
+        super(in);
+        mCarouselItems = in.createTypedArrayList(CarouselItem.CREATOR);
+        mCarouselAction = in.readTypedObject(SmartspaceTapAction.CREATOR);
+    }
+
+    private SmartspaceCarouselUiTemplateData(@SmartspaceTarget.UiTemplateType int templateType,
+            @Nullable CharSequence titleText,
+            @Nullable SmartspaceIcon titleIcon,
+            @Nullable CharSequence subtitleText,
+            @Nullable SmartspaceIcon subTitleIcon,
+            @Nullable SmartspaceTapAction primaryTapAction,
+            @Nullable CharSequence supplementalSubtitleText,
+            @Nullable SmartspaceIcon supplementalSubtitleIcon,
+            @Nullable SmartspaceTapAction supplementalSubtitleTapAction,
+            @Nullable CharSequence supplementalAlarmText,
+            @NonNull List<CarouselItem> carouselItems,
+            @Nullable SmartspaceTapAction carouselAction) {
+        super(templateType, titleText, titleIcon, subtitleText, subTitleIcon, primaryTapAction,
+                supplementalSubtitleText, supplementalSubtitleIcon, supplementalSubtitleTapAction,
+                supplementalAlarmText);
+        mCarouselItems = carouselItems;
+        mCarouselAction = carouselAction;
+    }
+
+    @NonNull
+    public List<CarouselItem> getCarouselItems() {
+        return mCarouselItems;
+    }
+
+    @Nullable
+    public SmartspaceTapAction getCarouselAction() {
+        return mCarouselAction;
+    }
+
+    /**
+     * @see Parcelable.Creator
+     */
+    @NonNull
+    public static final Creator<SmartspaceCarouselUiTemplateData> CREATOR =
+            new Creator<SmartspaceCarouselUiTemplateData>() {
+                @Override
+                public SmartspaceCarouselUiTemplateData createFromParcel(Parcel in) {
+                    return new SmartspaceCarouselUiTemplateData(in);
+                }
+
+                @Override
+                public SmartspaceCarouselUiTemplateData[] newArray(int size) {
+                    return new SmartspaceCarouselUiTemplateData[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        super.writeToParcel(out, flags);
+        out.writeTypedList(mCarouselItems);
+        out.writeTypedObject(mCarouselAction, flags);
+    }
+
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof SmartspaceCarouselUiTemplateData)) return false;
+        if (!super.equals(o)) return false;
+        SmartspaceCarouselUiTemplateData that = (SmartspaceCarouselUiTemplateData) o;
+        return mCarouselItems.equals(that.mCarouselItems) && Objects.equals(mCarouselAction,
+                that.mCarouselAction);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(super.hashCode(), mCarouselItems, mCarouselAction);
+    }
+
+    @Override
+    public String toString() {
+        return super.toString() + " + SmartspaceCarouselUiTemplateData{"
+                + "mCarouselItems=" + mCarouselItems
+                + ", mCarouselActions=" + mCarouselAction
+                + '}';
+    }
+
+    /**
+     * A builder for {@link SmartspaceCarouselUiTemplateData} object.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final class Builder extends SmartspaceDefaultUiTemplateData.Builder {
+
+        private final List<CarouselItem> mCarouselItems;
+        private SmartspaceTapAction mCarouselAction;
+
+        /**
+         * A builder for {@link SmartspaceCarouselUiTemplateData}.
+         */
+        public Builder(@NonNull List<CarouselItem> carouselItems) {
+            super(SmartspaceTarget.UI_TEMPLATE_CAROUSEL);
+            mCarouselItems = Objects.requireNonNull(carouselItems);
+        }
+
+        /**
+         * Sets the card tap action.
+         */
+        @NonNull
+        public Builder setCarouselAction(@NonNull SmartspaceTapAction carouselAction) {
+            mCarouselAction = carouselAction;
+            return this;
+        }
+
+        /**
+         * Builds a new SmartspaceCarouselUiTemplateData instance.
+         *
+         * @throws IllegalStateException if the carousel data is invalid.
+         */
+        @NonNull
+        public SmartspaceCarouselUiTemplateData build() {
+            if (mCarouselItems.isEmpty()) {
+                throw new IllegalStateException("Carousel data is empty");
+            }
+            return new SmartspaceCarouselUiTemplateData(getTemplateType(), getTitleText(),
+                    getTitleIcon(), getSubtitleText(), getSubTitleIcon(), getPrimaryTapAction(),
+                    getSupplementalSubtitleText(), getSupplementalSubtitleIcon(),
+                    getSupplementalSubtitleTapAction(), getSupplementalAlarmText(),
+                    mCarouselItems,
+                    mCarouselAction);
+        }
+    }
+
+    /** Holds all the relevant data needed to render a carousel item. */
+    public static final class CarouselItem implements Parcelable {
+
+        /** Text which is above the image item. */
+        @Nullable
+        private final CharSequence mUpperText;
+
+        /** Image item. Can be empty. */
+        @Nullable
+        private final SmartspaceIcon mImage;
+
+        /** Text which is under the image item. */
+        @Nullable
+        private final CharSequence mLowerText;
+
+        /**
+         * Tap action for this {@link CarouselItem} instance. {@code mCarouselAction} is used if not
+         * being set.
+         */
+        @Nullable
+        private final SmartspaceTapAction mTapAction;
+
+        CarouselItem(@NonNull Parcel in) {
+            mUpperText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+            mImage = in.readTypedObject(SmartspaceIcon.CREATOR);
+            mLowerText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+            mTapAction = in.readTypedObject(SmartspaceTapAction.CREATOR);
+        }
+
+        private CarouselItem(@Nullable CharSequence upperText, @Nullable SmartspaceIcon image,
+                @Nullable CharSequence lowerText, @Nullable SmartspaceTapAction tapAction) {
+            mUpperText = upperText;
+            mImage = image;
+            mLowerText = lowerText;
+            mTapAction = tapAction;
+        }
+
+        @Nullable
+        public CharSequence getUpperText() {
+            return mUpperText;
+        }
+
+        @Nullable
+        public SmartspaceIcon getImage() {
+            return mImage;
+        }
+
+        @Nullable
+        public CharSequence getLowerText() {
+            return mLowerText;
+        }
+
+        @Nullable
+        public SmartspaceTapAction getTapAction() {
+            return mTapAction;
+        }
+
+        /**
+         * @see Parcelable.Creator
+         */
+        @NonNull
+        public static final Creator<CarouselItem> CREATOR =
+                new Creator<CarouselItem>() {
+                    @Override
+                    public CarouselItem createFromParcel(Parcel in) {
+                        return new CarouselItem(in);
+                    }
+
+                    @Override
+                    public CarouselItem[] newArray(int size) {
+                        return new CarouselItem[size];
+                    }
+                };
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel out, int flags) {
+            TextUtils.writeToParcel(mUpperText, out, flags);
+            out.writeTypedObject(mImage, flags);
+            TextUtils.writeToParcel(mLowerText, out, flags);
+            out.writeTypedObject(mTapAction, flags);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof CarouselItem)) return false;
+            CarouselItem that = (CarouselItem) o;
+            return SmartspaceUtils.isEqual(mUpperText, that.mUpperText) && Objects.equals(
+                    mImage,
+                    that.mImage) && SmartspaceUtils.isEqual(mLowerText, that.mLowerText)
+                    && Objects.equals(mTapAction, that.mTapAction);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mUpperText, mImage, mLowerText, mTapAction);
+        }
+
+        @Override
+        public String toString() {
+            return "CarouselItem{"
+                    + "mUpperText=" + mUpperText
+                    + ", mImage=" + mImage
+                    + ", mLowerText=" + mLowerText
+                    + ", mTapAction=" + mTapAction
+                    + '}';
+        }
+
+        /**
+         * A builder for {@link CarouselItem} object.
+         *
+         * @hide
+         */
+        @SystemApi
+        public static final class Builder {
+
+            private CharSequence mUpperText;
+            private SmartspaceIcon mImage;
+            private CharSequence mLowerText;
+            private SmartspaceTapAction mTapAction;
+
+            /**
+             * Sets the upper text.
+             */
+            @NonNull
+            public Builder setUpperText(@Nullable CharSequence upperText) {
+                mUpperText = upperText;
+                return this;
+            }
+
+            /**
+             * Sets the image.
+             */
+            @NonNull
+            public Builder setImage(@Nullable SmartspaceIcon image) {
+                mImage = image;
+                return this;
+            }
+
+
+            /**
+             * Sets the lower text.
+             */
+            @NonNull
+            public Builder setLowerText(@Nullable CharSequence lowerText) {
+                mLowerText = lowerText;
+                return this;
+            }
+
+            /**
+             * Sets the tap action.
+             */
+            @NonNull
+            public Builder setTapAction(@Nullable SmartspaceTapAction tapAction) {
+                mTapAction = tapAction;
+                return this;
+            }
+
+            /**
+             * Builds a new CarouselItem instance.
+             *
+             * @throws IllegalStateException if all the rendering data is empty.
+             */
+            @NonNull
+            public CarouselItem build() {
+                if (TextUtils.isEmpty(mUpperText) && mImage == null && TextUtils.isEmpty(
+                        mLowerText)) {
+                    throw new IllegalStateException("Carousel data is empty");
+                }
+                return new CarouselItem(mUpperText, mImage, mLowerText, mTapAction);
+            }
+        }
+    }
+}
diff --git a/core/java/android/app/smartspace/uitemplatedata/SmartspaceCombinedCardsUiTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/SmartspaceCombinedCardsUiTemplateData.java
new file mode 100644
index 0000000..7e2f74e
--- /dev/null
+++ b/core/java/android/app/smartspace/uitemplatedata/SmartspaceCombinedCardsUiTemplateData.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 android.app.smartspace.uitemplatedata;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.smartspace.SmartspaceTarget;
+import android.os.Parcel;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Holds all the relevant data needed to render a Smartspace card with the combined-card Ui
+ * Template.
+ *
+ * We only support 1 sub-list card combined with 1 carousel card. And we may expand our supported
+ * combinations in the future.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceCombinedCardsUiTemplateData extends SmartspaceDefaultUiTemplateData {
+
+    /** A list of secondary cards. */
+    @NonNull
+    private final List<SmartspaceDefaultUiTemplateData> mCombinedCardDataList;
+
+    SmartspaceCombinedCardsUiTemplateData(@NonNull Parcel in) {
+        super(in);
+        mCombinedCardDataList = in.createTypedArrayList(SmartspaceDefaultUiTemplateData.CREATOR);
+    }
+
+    private SmartspaceCombinedCardsUiTemplateData(@SmartspaceTarget.UiTemplateType int templateType,
+            @Nullable CharSequence titleText,
+            @Nullable SmartspaceIcon titleIcon,
+            @Nullable CharSequence subtitleText,
+            @Nullable SmartspaceIcon subTitleIcon,
+            @Nullable SmartspaceTapAction primaryTapAction,
+            @Nullable CharSequence supplementalSubtitleText,
+            @Nullable SmartspaceIcon supplementalSubtitleIcon,
+            @Nullable SmartspaceTapAction supplementalSubtitleTapAction,
+            @Nullable CharSequence supplementalAlarmText,
+            @NonNull List<SmartspaceDefaultUiTemplateData> combinedCardDataList) {
+        super(templateType, titleText, titleIcon, subtitleText, subTitleIcon, primaryTapAction,
+                supplementalSubtitleText, supplementalSubtitleIcon, supplementalSubtitleTapAction,
+                supplementalAlarmText);
+        mCombinedCardDataList = combinedCardDataList;
+    }
+
+    @NonNull
+    public List<SmartspaceDefaultUiTemplateData> getCombinedCardDataList() {
+        return mCombinedCardDataList;
+    }
+
+    /**
+     * @see Parcelable.Creator
+     */
+    @NonNull
+    public static final Creator<SmartspaceCombinedCardsUiTemplateData> CREATOR =
+            new Creator<SmartspaceCombinedCardsUiTemplateData>() {
+                @Override
+                public SmartspaceCombinedCardsUiTemplateData createFromParcel(Parcel in) {
+                    return new SmartspaceCombinedCardsUiTemplateData(in);
+                }
+
+                @Override
+                public SmartspaceCombinedCardsUiTemplateData[] newArray(int size) {
+                    return new SmartspaceCombinedCardsUiTemplateData[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        super.writeToParcel(out, flags);
+        out.writeTypedList(mCombinedCardDataList);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof SmartspaceCombinedCardsUiTemplateData)) return false;
+        if (!super.equals(o)) return false;
+        SmartspaceCombinedCardsUiTemplateData that = (SmartspaceCombinedCardsUiTemplateData) o;
+        return mCombinedCardDataList.equals(that.mCombinedCardDataList);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(super.hashCode(), mCombinedCardDataList);
+    }
+
+    @Override
+    public String toString() {
+        return super.toString() + " + SmartspaceCombinedCardsUiTemplateData{"
+                + "mCombinedCardDataList=" + mCombinedCardDataList
+                + '}';
+    }
+
+    /**
+     * A builder for {@link SmartspaceCombinedCardsUiTemplateData} object.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final class Builder extends SmartspaceDefaultUiTemplateData.Builder {
+
+        private final List<SmartspaceDefaultUiTemplateData> mCombinedCardDataList;
+
+        /**
+         * A builder for {@link SmartspaceCombinedCardsUiTemplateData}.
+         */
+        public Builder(@NonNull List<SmartspaceDefaultUiTemplateData> combinedCardDataList) {
+            super(SmartspaceTarget.UI_TEMPLATE_COMBINED_CARDS);
+            mCombinedCardDataList = Objects.requireNonNull(combinedCardDataList);
+        }
+
+        /**
+         * Builds a new SmartspaceCombinedCardsUiTemplateData instance.
+         *
+         * @throws IllegalStateException if any required non-null field is null
+         */
+        @NonNull
+        public SmartspaceCombinedCardsUiTemplateData build() {
+            if (mCombinedCardDataList == null) {
+                throw new IllegalStateException("Please assign a value to all @NonNull args.");
+            }
+            return new SmartspaceCombinedCardsUiTemplateData(getTemplateType(), getTitleText(),
+                    getTitleIcon(), getSubtitleText(), getSubTitleIcon(), getPrimaryTapAction(),
+                    getSupplementalSubtitleText(), getSupplementalSubtitleIcon(),
+                    getSupplementalSubtitleTapAction(), getSupplementalAlarmText(),
+                    mCombinedCardDataList);
+        }
+    }
+}
diff --git a/core/java/android/app/smartspace/uitemplatedata/SmartspaceDefaultUiTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/SmartspaceDefaultUiTemplateData.java
new file mode 100644
index 0000000..742d5c9
--- /dev/null
+++ b/core/java/android/app/smartspace/uitemplatedata/SmartspaceDefaultUiTemplateData.java
@@ -0,0 +1,459 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.smartspace.uitemplatedata;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.app.smartspace.SmartspaceTarget.UiTemplateType;
+import android.app.smartspace.SmartspaceUtils;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.Objects;
+
+/**
+ * Holds all the relevant data needed to render a Smartspace card with the default Ui Template.
+ *
+ * @hide
+ */
+@SystemApi
+@SuppressLint("ParcelNotFinal")
+public class SmartspaceDefaultUiTemplateData implements Parcelable {
+
+    /**
+     * {@link UiTemplateType} indicating the template type of this template data.
+     *
+     * @see UiTemplateType
+     */
+    @UiTemplateType
+    private final int mTemplateType;
+
+    /**
+     * Title text and title icon are shown at the first row. When both are absent, the date view
+     * will be used, which has its own tap action applied to the title area.
+     */
+    @Nullable
+    private final CharSequence mTitleText;
+
+    @Nullable
+    private final SmartspaceIcon mTitleIcon;
+
+    /** Subtitle text and icon are shown at the second row. */
+    @Nullable
+    private final CharSequence mSubtitleText;
+
+    @Nullable
+    private final SmartspaceIcon mSubTitleIcon;
+
+    /**
+     * Primary tap action for the entire card, including the blank spaces, except: 1. When title is
+     * absent, the date view's default tap action is used; 2. Supplemental subtitle uses its own tap
+     * action if being set; 3. Secondary card uses its own tap action if being set.
+     */
+    @Nullable
+    private final SmartspaceTapAction mPrimaryTapAction;
+
+    /**
+     * Supplemental subtitle text and icon are shown at the second row following the subtitle text.
+     * Mainly used for weather info on non-weather card.
+     */
+    @Nullable
+    private final CharSequence mSupplementalSubtitleText;
+
+    @Nullable
+    private final SmartspaceIcon mSupplementalSubtitleIcon;
+
+    /**
+     * Tap action for the supplemental subtitle's text and icon. Will use the primary tap action if
+     * not being set.
+     */
+    @Nullable
+    private final SmartspaceTapAction mSupplementalSubtitleTapAction;
+
+    /**
+     * Supplemental alarm text is specifically used for holiday alarm, which is appended to "next
+     * alarm".
+     */
+    @Nullable
+    private final CharSequence mSupplementalAlarmText;
+
+    SmartspaceDefaultUiTemplateData(@NonNull Parcel in) {
+        mTemplateType = in.readInt();
+        mTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+        mTitleIcon = in.readTypedObject(SmartspaceIcon.CREATOR);
+        mSubtitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+        mSubTitleIcon = in.readTypedObject(SmartspaceIcon.CREATOR);
+        mPrimaryTapAction = in.readTypedObject(SmartspaceTapAction.CREATOR);
+        mSupplementalSubtitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+        mSupplementalSubtitleIcon = in.readTypedObject(SmartspaceIcon.CREATOR);
+        mSupplementalSubtitleTapAction = in.readTypedObject(SmartspaceTapAction.CREATOR);
+        mSupplementalAlarmText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+    }
+
+    /**
+     * Should ONLY used by subclasses. For the general instance creation, please use
+     * SmartspaceDefaultUiTemplateData.Builder.
+     */
+    SmartspaceDefaultUiTemplateData(@UiTemplateType int templateType,
+            @Nullable CharSequence titleText,
+            @Nullable SmartspaceIcon titleIcon,
+            @Nullable CharSequence subtitleText,
+            @Nullable SmartspaceIcon subTitleIcon,
+            @Nullable SmartspaceTapAction primaryTapAction,
+            @Nullable CharSequence supplementalSubtitleText,
+            @Nullable SmartspaceIcon supplementalSubtitleIcon,
+            @Nullable SmartspaceTapAction supplementalSubtitleTapAction,
+            @Nullable CharSequence supplementalAlarmText) {
+        mTemplateType = templateType;
+        mTitleText = titleText;
+        mTitleIcon = titleIcon;
+        mSubtitleText = subtitleText;
+        mSubTitleIcon = subTitleIcon;
+        mPrimaryTapAction = primaryTapAction;
+        mSupplementalSubtitleText = supplementalSubtitleText;
+        mSupplementalSubtitleIcon = supplementalSubtitleIcon;
+        mSupplementalSubtitleTapAction = supplementalSubtitleTapAction;
+        mSupplementalAlarmText = supplementalAlarmText;
+    }
+
+    @UiTemplateType
+    public int getTemplateType() {
+        return mTemplateType;
+    }
+
+    @Nullable
+    public CharSequence getTitleText() {
+        return mTitleText;
+    }
+
+    @Nullable
+    public SmartspaceIcon getTitleIcon() {
+        return mTitleIcon;
+    }
+
+    @Nullable
+    public CharSequence getSubtitleText() {
+        return mSubtitleText;
+    }
+
+    @Nullable
+    public SmartspaceIcon getSubTitleIcon() {
+        return mSubTitleIcon;
+    }
+
+    @Nullable
+    public CharSequence getSupplementalSubtitleText() {
+        return mSupplementalSubtitleText;
+    }
+
+    @Nullable
+    public SmartspaceIcon getSupplementalSubtitleIcon() {
+        return mSupplementalSubtitleIcon;
+    }
+
+    @Nullable
+    public SmartspaceTapAction getPrimaryTapAction() {
+        return mPrimaryTapAction;
+    }
+
+    @Nullable
+    public SmartspaceTapAction getSupplementalSubtitleTapAction() {
+        return mSupplementalSubtitleTapAction;
+    }
+
+    @Nullable
+    public CharSequence getSupplementalAlarmText() {
+        return mSupplementalAlarmText;
+    }
+
+    /**
+     * @see Parcelable.Creator
+     */
+    @NonNull
+    public static final Creator<SmartspaceDefaultUiTemplateData> CREATOR =
+            new Creator<SmartspaceDefaultUiTemplateData>() {
+                @Override
+                public SmartspaceDefaultUiTemplateData createFromParcel(Parcel in) {
+                    return new SmartspaceDefaultUiTemplateData(in);
+                }
+
+                @Override
+                public SmartspaceDefaultUiTemplateData[] newArray(int size) {
+                    return new SmartspaceDefaultUiTemplateData[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeInt(mTemplateType);
+        TextUtils.writeToParcel(mTitleText, out, flags);
+        out.writeTypedObject(mTitleIcon, flags);
+        TextUtils.writeToParcel(mSubtitleText, out, flags);
+        out.writeTypedObject(mSubTitleIcon, flags);
+        out.writeTypedObject(mPrimaryTapAction, flags);
+        TextUtils.writeToParcel(mSupplementalSubtitleText, out, flags);
+        out.writeTypedObject(mSupplementalSubtitleIcon, flags);
+        out.writeTypedObject(mSupplementalSubtitleTapAction, flags);
+        TextUtils.writeToParcel(mSupplementalAlarmText, out, flags);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof SmartspaceDefaultUiTemplateData)) return false;
+        SmartspaceDefaultUiTemplateData that = (SmartspaceDefaultUiTemplateData) o;
+        return mTemplateType == that.mTemplateType && SmartspaceUtils.isEqual(mTitleText,
+                that.mTitleText)
+                && Objects.equals(mTitleIcon, that.mTitleIcon)
+                && SmartspaceUtils.isEqual(mSubtitleText, that.mSubtitleText)
+                && Objects.equals(mSubTitleIcon, that.mSubTitleIcon)
+                && Objects.equals(mPrimaryTapAction, that.mPrimaryTapAction)
+                && SmartspaceUtils.isEqual(mSupplementalSubtitleText,
+                that.mSupplementalSubtitleText)
+                && Objects.equals(mSupplementalSubtitleIcon, that.mSupplementalSubtitleIcon)
+                && Objects.equals(mSupplementalSubtitleTapAction,
+                that.mSupplementalSubtitleTapAction)
+                && SmartspaceUtils.isEqual(mSupplementalAlarmText, that.mSupplementalAlarmText);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mTemplateType, mTitleText, mTitleIcon, mSubtitleText, mSubTitleIcon,
+                mPrimaryTapAction, mSupplementalSubtitleText, mSupplementalSubtitleIcon,
+                mSupplementalSubtitleTapAction, mSupplementalAlarmText);
+    }
+
+    @Override
+    public String toString() {
+        return "SmartspaceDefaultUiTemplateData{"
+                + "mTemplateType=" + mTemplateType
+                + ", mTitleText=" + mTitleText
+                + ", mTitleIcon=" + mTitleIcon
+                + ", mSubtitleText=" + mSubtitleText
+                + ", mSubTitleIcon=" + mSubTitleIcon
+                + ", mPrimaryTapAction=" + mPrimaryTapAction
+                + ", mSupplementalSubtitleText=" + mSupplementalSubtitleText
+                + ", mSupplementalSubtitleIcon=" + mSupplementalSubtitleIcon
+                + ", mSupplementalSubtitleTapAction=" + mSupplementalSubtitleTapAction
+                + ", mSupplementalAlarmText=" + mSupplementalAlarmText
+                + '}';
+    }
+
+    /**
+     * A builder for {@link SmartspaceDefaultUiTemplateData} object.
+     *
+     * @hide
+     */
+    @SystemApi
+    @SuppressLint("StaticFinalBuilder")
+    public static class Builder {
+        @UiTemplateType
+        private final int mTemplateType;
+        private CharSequence mTitleText;
+        private SmartspaceIcon mTitleIcon;
+        private CharSequence mSubtitleText;
+        private SmartspaceIcon mSubTitleIcon;
+        private SmartspaceTapAction mPrimaryTapAction;
+        private CharSequence mSupplementalSubtitleText;
+        private SmartspaceIcon mSupplementalSubtitleIcon;
+        private SmartspaceTapAction mSupplementalSubtitleTapAction;
+        private CharSequence mSupplementalAlarmText;
+
+        /**
+         * A builder for {@link SmartspaceDefaultUiTemplateData}.
+         *
+         * @param templateType the {@link UiTemplateType} of this template data.
+         */
+        public Builder(@UiTemplateType int templateType) {
+            mTemplateType = templateType;
+        }
+
+        /** Should ONLY be used by the subclasses */
+        @UiTemplateType
+        @SuppressLint("GetterOnBuilder")
+        int getTemplateType() {
+            return mTemplateType;
+        }
+
+        /** Should ONLY be used by the subclasses */
+        @Nullable
+        @SuppressLint("GetterOnBuilder")
+        CharSequence getTitleText() {
+            return mTitleText;
+        }
+
+        /** Should ONLY be used by the subclasses */
+        @Nullable
+        @SuppressLint("GetterOnBuilder")
+        SmartspaceIcon getTitleIcon() {
+            return mTitleIcon;
+        }
+
+        /** Should ONLY be used by the subclasses */
+        @Nullable
+        @SuppressLint("GetterOnBuilder")
+        CharSequence getSubtitleText() {
+            return mSubtitleText;
+        }
+
+        /** Should ONLY be used by the subclasses */
+        @Nullable
+        @SuppressLint("GetterOnBuilder")
+        SmartspaceIcon getSubTitleIcon() {
+            return mSubTitleIcon;
+        }
+
+        /** Should ONLY be used by the subclasses */
+        @Nullable
+        @SuppressLint("GetterOnBuilder")
+        SmartspaceTapAction getPrimaryTapAction() {
+            return mPrimaryTapAction;
+        }
+
+        /** Should ONLY be used by the subclasses */
+        @Nullable
+        @SuppressLint("GetterOnBuilder")
+        CharSequence getSupplementalSubtitleText() {
+            return mSupplementalSubtitleText;
+        }
+
+        /** Should ONLY be used by the subclasses */
+        @Nullable
+        @SuppressLint("GetterOnBuilder")
+        SmartspaceIcon getSupplementalSubtitleIcon() {
+            return mSupplementalSubtitleIcon;
+        }
+
+        /** Should ONLY be used by the subclasses */
+        @Nullable
+        @SuppressLint("GetterOnBuilder")
+        SmartspaceTapAction getSupplementalSubtitleTapAction() {
+            return mSupplementalSubtitleTapAction;
+        }
+
+        /** Should ONLY be used by the subclasses */
+        @Nullable
+        @SuppressLint("GetterOnBuilder")
+        CharSequence getSupplementalAlarmText() {
+            return mSupplementalAlarmText;
+        }
+
+        /**
+         * Sets the card title.
+         */
+        @NonNull
+        public Builder setTitleText(@NonNull CharSequence titleText) {
+            mTitleText = titleText;
+            return this;
+        }
+
+        /**
+         * Sets the card title icon.
+         */
+        @NonNull
+        public Builder setTitleIcon(@NonNull SmartspaceIcon titleIcon) {
+            mTitleIcon = titleIcon;
+            return this;
+        }
+
+        /**
+         * Sets the card subtitle.
+         */
+        @NonNull
+        public Builder setSubtitleText(@NonNull CharSequence subtitleText) {
+            mSubtitleText = subtitleText;
+            return this;
+        }
+
+        /**
+         * Sets the card subtitle icon.
+         */
+        @NonNull
+        public Builder setSubTitleIcon(@NonNull SmartspaceIcon subTitleIcon) {
+            mSubTitleIcon = subTitleIcon;
+            return this;
+        }
+
+        /**
+         * Sets the card primary tap action.
+         */
+        @NonNull
+        public Builder setPrimaryTapAction(@NonNull SmartspaceTapAction primaryTapAction) {
+            mPrimaryTapAction = primaryTapAction;
+            return this;
+        }
+
+        /**
+         * Sets the supplemental subtitle text.
+         */
+        @NonNull
+        public Builder setSupplementalSubtitleText(@NonNull CharSequence supplementalSubtitleText) {
+            mSupplementalSubtitleText = supplementalSubtitleText;
+            return this;
+        }
+
+        /**
+         * Sets the supplemental subtitle icon.
+         */
+        @NonNull
+        public Builder setSupplementalSubtitleIcon(
+                @NonNull SmartspaceIcon supplementalSubtitleIcon) {
+            mSupplementalSubtitleIcon = supplementalSubtitleIcon;
+            return this;
+        }
+
+        /**
+         * Sets the supplemental subtitle tap action. {@code mPrimaryTapAction} will be used if not
+         * being
+         * set.
+         */
+        @NonNull
+        public Builder setSupplementalSubtitleTapAction(
+                @NonNull SmartspaceTapAction supplementalSubtitleTapAction) {
+            mSupplementalSubtitleTapAction = supplementalSubtitleTapAction;
+            return this;
+        }
+
+        /**
+         * Sets the supplemental alarm text.
+         */
+        @NonNull
+        public Builder setSupplementalAlarmText(@NonNull CharSequence supplementalAlarmText) {
+            mSupplementalAlarmText = supplementalAlarmText;
+            return this;
+        }
+
+        /**
+         * Builds a new SmartspaceDefaultUiTemplateData instance.
+         */
+        @NonNull
+        public SmartspaceDefaultUiTemplateData build() {
+            return new SmartspaceDefaultUiTemplateData(mTemplateType, mTitleText, mTitleIcon,
+                    mSubtitleText, mSubTitleIcon, mPrimaryTapAction, mSupplementalSubtitleText,
+                    mSupplementalSubtitleIcon, mSupplementalSubtitleTapAction,
+                    mSupplementalAlarmText);
+        }
+    }
+}
diff --git a/core/java/android/app/smartspace/uitemplatedata/SmartspaceHeadToHeadUiTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/SmartspaceHeadToHeadUiTemplateData.java
new file mode 100644
index 0000000..c76af27
--- /dev/null
+++ b/core/java/android/app/smartspace/uitemplatedata/SmartspaceHeadToHeadUiTemplateData.java
@@ -0,0 +1,286 @@
+/*
+ * 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.app.smartspace.uitemplatedata;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.smartspace.SmartspaceTarget;
+import android.app.smartspace.SmartspaceUtils;
+import android.os.Parcel;
+import android.text.TextUtils;
+
+import java.util.Objects;
+
+/**
+ * Holds all the relevant data needed to render a Smartspace card with the head-to-head Ui Template.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceHeadToHeadUiTemplateData extends SmartspaceDefaultUiTemplateData {
+
+    @Nullable
+    private final CharSequence mHeadToHeadTitle;
+    @Nullable
+    private final SmartspaceIcon mHeadToHeadFirstCompetitorIcon;
+    @Nullable
+    private final SmartspaceIcon mHeadToHeadSecondCompetitorIcon;
+    @Nullable
+    private final CharSequence mHeadToHeadFirstCompetitorText;
+    @Nullable
+    private final CharSequence mHeadToHeadSecondCompetitorText;
+
+    /** Tap action for the head-to-head secondary card. */
+    @Nullable
+    private final SmartspaceTapAction mHeadToHeadAction;
+
+    SmartspaceHeadToHeadUiTemplateData(@NonNull Parcel in) {
+        super(in);
+        mHeadToHeadTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+        mHeadToHeadFirstCompetitorIcon = in.readTypedObject(SmartspaceIcon.CREATOR);
+        mHeadToHeadSecondCompetitorIcon = in.readTypedObject(SmartspaceIcon.CREATOR);
+        mHeadToHeadFirstCompetitorText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+        mHeadToHeadSecondCompetitorText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+        mHeadToHeadAction = in.readTypedObject(SmartspaceTapAction.CREATOR);
+    }
+
+    private SmartspaceHeadToHeadUiTemplateData(@SmartspaceTarget.UiTemplateType int templateType,
+            @Nullable CharSequence titleText,
+            @Nullable SmartspaceIcon titleIcon,
+            @Nullable CharSequence subtitleText,
+            @Nullable SmartspaceIcon subTitleIcon,
+            @Nullable SmartspaceTapAction primaryTapAction,
+            @Nullable CharSequence supplementalSubtitleText,
+            @Nullable SmartspaceIcon supplementalSubtitleIcon,
+            @Nullable SmartspaceTapAction supplementalSubtitleTapAction,
+            @Nullable CharSequence supplementalAlarmText,
+            @Nullable CharSequence headToHeadTitle,
+            @Nullable SmartspaceIcon headToHeadFirstCompetitorIcon,
+            @Nullable SmartspaceIcon headToHeadSecondCompetitorIcon,
+            @Nullable CharSequence headToHeadFirstCompetitorText,
+            @Nullable CharSequence headToHeadSecondCompetitorText,
+            @Nullable SmartspaceTapAction headToHeadAction) {
+        super(templateType, titleText, titleIcon, subtitleText, subTitleIcon, primaryTapAction,
+                supplementalSubtitleText, supplementalSubtitleIcon, supplementalSubtitleTapAction,
+                supplementalAlarmText);
+        mHeadToHeadTitle = headToHeadTitle;
+        mHeadToHeadFirstCompetitorIcon = headToHeadFirstCompetitorIcon;
+        mHeadToHeadSecondCompetitorIcon = headToHeadSecondCompetitorIcon;
+        mHeadToHeadFirstCompetitorText = headToHeadFirstCompetitorText;
+        mHeadToHeadSecondCompetitorText = headToHeadSecondCompetitorText;
+        mHeadToHeadAction = headToHeadAction;
+    }
+
+    @Nullable
+    public CharSequence getHeadToHeadTitle() {
+        return mHeadToHeadTitle;
+    }
+
+    @Nullable
+    public SmartspaceIcon getHeadToHeadFirstCompetitorIcon() {
+        return mHeadToHeadFirstCompetitorIcon;
+    }
+
+    @Nullable
+    public SmartspaceIcon getHeadToHeadSecondCompetitorIcon() {
+        return mHeadToHeadSecondCompetitorIcon;
+    }
+
+    @Nullable
+    public CharSequence getHeadToHeadFirstCompetitorText() {
+        return mHeadToHeadFirstCompetitorText;
+    }
+
+    @Nullable
+    public CharSequence getHeadToHeadSecondCompetitorText() {
+        return mHeadToHeadSecondCompetitorText;
+    }
+
+    @Nullable
+    public SmartspaceTapAction getHeadToHeadAction() {
+        return mHeadToHeadAction;
+    }
+
+    /**
+     * @see Parcelable.Creator
+     */
+    @NonNull
+    public static final Creator<SmartspaceHeadToHeadUiTemplateData> CREATOR =
+            new Creator<SmartspaceHeadToHeadUiTemplateData>() {
+                @Override
+                public SmartspaceHeadToHeadUiTemplateData createFromParcel(Parcel in) {
+                    return new SmartspaceHeadToHeadUiTemplateData(in);
+                }
+
+                @Override
+                public SmartspaceHeadToHeadUiTemplateData[] newArray(int size) {
+                    return new SmartspaceHeadToHeadUiTemplateData[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        super.writeToParcel(out, flags);
+        TextUtils.writeToParcel(mHeadToHeadTitle, out, flags);
+        out.writeTypedObject(mHeadToHeadFirstCompetitorIcon, flags);
+        out.writeTypedObject(mHeadToHeadSecondCompetitorIcon, flags);
+        TextUtils.writeToParcel(mHeadToHeadFirstCompetitorText, out, flags);
+        TextUtils.writeToParcel(mHeadToHeadSecondCompetitorText, out, flags);
+        out.writeTypedObject(mHeadToHeadAction, flags);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof SmartspaceHeadToHeadUiTemplateData)) return false;
+        if (!super.equals(o)) return false;
+        SmartspaceHeadToHeadUiTemplateData that = (SmartspaceHeadToHeadUiTemplateData) o;
+        return SmartspaceUtils.isEqual(mHeadToHeadTitle, that.mHeadToHeadTitle) && Objects.equals(
+                mHeadToHeadFirstCompetitorIcon, that.mHeadToHeadFirstCompetitorIcon)
+                && Objects.equals(
+                mHeadToHeadSecondCompetitorIcon, that.mHeadToHeadSecondCompetitorIcon)
+                && SmartspaceUtils.isEqual(mHeadToHeadFirstCompetitorText,
+                that.mHeadToHeadFirstCompetitorText)
+                && SmartspaceUtils.isEqual(mHeadToHeadSecondCompetitorText,
+                that.mHeadToHeadSecondCompetitorText)
+                && Objects.equals(
+                mHeadToHeadAction, that.mHeadToHeadAction);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(super.hashCode(), mHeadToHeadTitle, mHeadToHeadFirstCompetitorIcon,
+                mHeadToHeadSecondCompetitorIcon, mHeadToHeadFirstCompetitorText,
+                mHeadToHeadSecondCompetitorText,
+                mHeadToHeadAction);
+    }
+
+    @Override
+    public String toString() {
+        return super.toString() + " + SmartspaceHeadToHeadUiTemplateData{"
+                + "mH2HTitle=" + mHeadToHeadTitle
+                + ", mH2HFirstCompetitorIcon=" + mHeadToHeadFirstCompetitorIcon
+                + ", mH2HSecondCompetitorIcon=" + mHeadToHeadSecondCompetitorIcon
+                + ", mH2HFirstCompetitorText=" + mHeadToHeadFirstCompetitorText
+                + ", mH2HSecondCompetitorText=" + mHeadToHeadSecondCompetitorText
+                + ", mH2HAction=" + mHeadToHeadAction
+                + '}';
+    }
+
+    /**
+     * A builder for {@link SmartspaceHeadToHeadUiTemplateData} object.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final class Builder extends SmartspaceDefaultUiTemplateData.Builder {
+
+        private CharSequence mHeadToHeadTitle;
+        private SmartspaceIcon mHeadToHeadFirstCompetitorIcon;
+        private SmartspaceIcon mHeadToHeadSecondCompetitorIcon;
+        private CharSequence mHeadToHeadFirstCompetitorText;
+        private CharSequence mHeadToHeadSecondCompetitorText;
+        private SmartspaceTapAction mHeadToHeadAction;
+
+        /**
+         * A builder for {@link SmartspaceHeadToHeadUiTemplateData}.
+         */
+        public Builder() {
+            super(SmartspaceTarget.UI_TEMPLATE_HEAD_TO_HEAD);
+        }
+
+        /**
+         * Sets the head-to-head card's title
+         */
+        @NonNull
+        public Builder setHeadToHeadTitle(@Nullable CharSequence headToHeadTitle) {
+            mHeadToHeadTitle = headToHeadTitle;
+            return this;
+        }
+
+        /**
+         * Sets the head-to-head card's first competitor icon
+         */
+        @NonNull
+        public Builder setHeadToHeadFirstCompetitorIcon(
+                @Nullable SmartspaceIcon headToHeadFirstCompetitorIcon) {
+            mHeadToHeadFirstCompetitorIcon = headToHeadFirstCompetitorIcon;
+            return this;
+        }
+
+        /**
+         * Sets the head-to-head card's second competitor icon
+         */
+        @NonNull
+        public Builder setHeadToHeadSecondCompetitorIcon(
+                @Nullable SmartspaceIcon headToHeadSecondCompetitorIcon) {
+            mHeadToHeadSecondCompetitorIcon = headToHeadSecondCompetitorIcon;
+            return this;
+        }
+
+        /**
+         * Sets the head-to-head card's first competitor text
+         */
+        @NonNull
+        public Builder setHeadToHeadFirstCompetitorText(
+                @Nullable CharSequence headToHeadFirstCompetitorText) {
+            mHeadToHeadFirstCompetitorText = headToHeadFirstCompetitorText;
+            return this;
+        }
+
+        /**
+         * Sets the head-to-head card's second competitor text
+         */
+        @NonNull
+        public Builder setHeadToHeadSecondCompetitorText(
+                @Nullable CharSequence headToHeadSecondCompetitorText) {
+            mHeadToHeadSecondCompetitorText = headToHeadSecondCompetitorText;
+            return this;
+        }
+
+        /**
+         * Sets the head-to-head card's tap action
+         */
+        @NonNull
+        public Builder setHeadToHeadAction(@Nullable SmartspaceTapAction headToHeadAction) {
+            mHeadToHeadAction = headToHeadAction;
+            return this;
+        }
+
+        /**
+         * Builds a new SmartspaceHeadToHeadUiTemplateData instance.
+         */
+        @NonNull
+        public SmartspaceHeadToHeadUiTemplateData build() {
+            return new SmartspaceHeadToHeadUiTemplateData(getTemplateType(), getTitleText(),
+                    getTitleIcon(), getSubtitleText(), getSubTitleIcon(), getPrimaryTapAction(),
+                    getSupplementalSubtitleText(), getSupplementalSubtitleIcon(),
+                    getSupplementalSubtitleTapAction(), getSupplementalAlarmText(),
+                    mHeadToHeadTitle,
+                    mHeadToHeadFirstCompetitorIcon,
+                    mHeadToHeadSecondCompetitorIcon, mHeadToHeadFirstCompetitorText,
+                    mHeadToHeadSecondCompetitorText,
+                    mHeadToHeadAction);
+        }
+    }
+}
diff --git a/core/java/android/app/smartspace/uitemplatedata/SmartspaceIcon.java b/core/java/android/app/smartspace/uitemplatedata/SmartspaceIcon.java
new file mode 100644
index 0000000..70b3095
--- /dev/null
+++ b/core/java/android/app/smartspace/uitemplatedata/SmartspaceIcon.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.smartspace.uitemplatedata;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.smartspace.SmartspaceUtils;
+import android.graphics.drawable.Icon;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.Objects;
+
+/**
+ * Holds the information for a Smartspace-card icon. Including the icon image itself, and an
+ * optional content description as the icon's accessibility description.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceIcon implements Parcelable {
+
+    @NonNull
+    private final Icon mIcon;
+
+    @Nullable
+    private final CharSequence mContentDescription;
+
+    SmartspaceIcon(@NonNull Parcel in) {
+        mIcon = in.readTypedObject(Icon.CREATOR);
+        mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+    }
+
+    private SmartspaceIcon(@NonNull Icon icon, @Nullable CharSequence contentDescription) {
+        mIcon = icon;
+        mContentDescription = contentDescription;
+    }
+
+    @NonNull
+    public Icon getIcon() {
+        return mIcon;
+    }
+
+    @Nullable
+    public CharSequence getContentDescription() {
+        return mContentDescription;
+    }
+
+    @NonNull
+    public static final Creator<SmartspaceIcon> CREATOR = new Creator<SmartspaceIcon>() {
+        @Override
+        public SmartspaceIcon createFromParcel(Parcel in) {
+            return new SmartspaceIcon(in);
+        }
+
+        @Override
+        public SmartspaceIcon[] newArray(int size) {
+            return new SmartspaceIcon[size];
+        }
+    };
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof SmartspaceIcon)) return false;
+        SmartspaceIcon that = (SmartspaceIcon) o;
+        return mIcon.equals(that.mIcon) && SmartspaceUtils.isEqual(mContentDescription,
+                that.mContentDescription);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mIcon, mContentDescription);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeTypedObject(mIcon, flags);
+        TextUtils.writeToParcel(mContentDescription, out, flags);
+    }
+
+    @Override
+    public String toString() {
+        return "SmartspaceIcon{"
+                + "mImage=" + mIcon
+                + ", mContentDescription='" + mContentDescription + '\''
+                + '}';
+    }
+
+    /**
+     * A builder for {@link SmartspaceIcon} object.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final class Builder {
+
+        private Icon mIcon;
+        private CharSequence mContentDescription;
+
+        /**
+         * A builder for {@link SmartspaceIcon}.
+         *
+         * @param icon the icon image of this smartspace icon.
+         */
+        public Builder(@NonNull Icon icon) {
+            mIcon = Objects.requireNonNull(icon);
+        }
+
+        /**
+         * Sets the icon's content description.
+         */
+        @NonNull
+        public Builder setContentDescription(@NonNull CharSequence contentDescription) {
+            mContentDescription = contentDescription;
+            return this;
+        }
+
+        /**
+         * Builds a new SmartspaceIcon instance.
+         */
+        @NonNull
+        public SmartspaceIcon build() {
+            return new SmartspaceIcon(mIcon, mContentDescription);
+        }
+    }
+}
diff --git a/core/java/android/app/smartspace/uitemplatedata/SmartspaceSubCardUiTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/SmartspaceSubCardUiTemplateData.java
new file mode 100644
index 0000000..287cf8e
--- /dev/null
+++ b/core/java/android/app/smartspace/uitemplatedata/SmartspaceSubCardUiTemplateData.java
@@ -0,0 +1,198 @@
+/*
+ * 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.app.smartspace.uitemplatedata;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.smartspace.SmartspaceTarget;
+import android.app.smartspace.SmartspaceUtils;
+import android.os.Parcel;
+import android.text.TextUtils;
+
+import java.util.Objects;
+
+/**
+ * Holds all the relevant data needed to render a Smartspace card with the sub-card Ui Template.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceSubCardUiTemplateData extends SmartspaceDefaultUiTemplateData {
+
+    /** Icon for the sub-card. */
+    @NonNull
+    private final SmartspaceIcon mSubCardIcon;
+
+    /** Text for the sub-card, which shows below the icon when being set. */
+    @Nullable
+    private final CharSequence mSubCardText;
+
+    /** Tap action for the sub-card secondary card. */
+    @Nullable
+    private final SmartspaceTapAction mSubCardAction;
+
+    SmartspaceSubCardUiTemplateData(@NonNull Parcel in) {
+        super(in);
+        mSubCardIcon = in.readTypedObject(SmartspaceIcon.CREATOR);
+        mSubCardText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+        mSubCardAction = in.readTypedObject(SmartspaceTapAction.CREATOR);
+    }
+
+    private SmartspaceSubCardUiTemplateData(int templateType,
+            @Nullable CharSequence titleText,
+            @Nullable SmartspaceIcon titleIcon,
+            @Nullable CharSequence subtitleText,
+            @Nullable SmartspaceIcon subTitleIcon,
+            @Nullable SmartspaceTapAction primaryTapAction,
+            @Nullable CharSequence supplementalSubtitleText,
+            @Nullable SmartspaceIcon supplementalSubtitleIcon,
+            @Nullable SmartspaceTapAction supplementalSubtitleTapAction,
+            @Nullable CharSequence supplementalAlarmText,
+            @NonNull SmartspaceIcon subCardIcon,
+            @Nullable CharSequence subCardText,
+            @Nullable SmartspaceTapAction subCardAction) {
+        super(templateType, titleText, titleIcon, subtitleText, subTitleIcon, primaryTapAction,
+                supplementalSubtitleText, supplementalSubtitleIcon, supplementalSubtitleTapAction,
+                supplementalAlarmText);
+        mSubCardIcon = subCardIcon;
+        mSubCardText = subCardText;
+        mSubCardAction = subCardAction;
+    }
+
+    @NonNull
+    public SmartspaceIcon getSubCardIcon() {
+        return mSubCardIcon;
+    }
+
+    @Nullable
+    public CharSequence getSubCardText() {
+        return mSubCardText;
+    }
+
+    @Nullable
+    public SmartspaceTapAction getSubCardAction() {
+        return mSubCardAction;
+    }
+
+    /**
+     * @see Parcelable.Creator
+     */
+    @NonNull
+    public static final Creator<SmartspaceSubCardUiTemplateData> CREATOR =
+            new Creator<SmartspaceSubCardUiTemplateData>() {
+                @Override
+                public SmartspaceSubCardUiTemplateData createFromParcel(Parcel in) {
+                    return new SmartspaceSubCardUiTemplateData(in);
+                }
+
+                @Override
+                public SmartspaceSubCardUiTemplateData[] newArray(int size) {
+                    return new SmartspaceSubCardUiTemplateData[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        super.writeToParcel(out, flags);
+        out.writeTypedObject(mSubCardIcon, flags);
+        TextUtils.writeToParcel(mSubCardText, out, flags);
+        out.writeTypedObject(mSubCardAction, flags);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof SmartspaceSubCardUiTemplateData)) return false;
+        if (!super.equals(o)) return false;
+        SmartspaceSubCardUiTemplateData that = (SmartspaceSubCardUiTemplateData) o;
+        return mSubCardIcon.equals(that.mSubCardIcon) && SmartspaceUtils.isEqual(mSubCardText,
+                that.mSubCardText) && Objects.equals(mSubCardAction,
+                that.mSubCardAction);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(super.hashCode(), mSubCardIcon, mSubCardText, mSubCardAction);
+    }
+
+    @Override
+    public String toString() {
+        return super.toString() + " + SmartspaceSubCardUiTemplateData{"
+                + "mSubCardIcon=" + mSubCardIcon
+                + ", mSubCardText=" + mSubCardText
+                + ", mSubCardAction=" + mSubCardAction
+                + '}';
+    }
+
+    /**
+     * A builder for {@link SmartspaceSubCardUiTemplateData} object.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final class Builder extends SmartspaceDefaultUiTemplateData.Builder {
+
+        private final SmartspaceIcon mSubCardIcon;
+        private CharSequence mSubCardText;
+        private SmartspaceTapAction mSubCardAction;
+
+        /**
+         * A builder for {@link SmartspaceSubCardUiTemplateData}.
+         */
+        public Builder(@NonNull SmartspaceIcon subCardIcon) {
+            super(SmartspaceTarget.UI_TEMPLATE_SUB_CARD);
+            mSubCardIcon = Objects.requireNonNull(subCardIcon);
+        }
+
+        /**
+         * Sets the card title text.
+         */
+        @NonNull
+        public Builder setSubCardAction(@NonNull CharSequence subCardTitleText) {
+            mSubCardText = subCardTitleText;
+            return this;
+        }
+
+        /**
+         * Sets the card tap action.
+         */
+        @NonNull
+        public Builder setSubCardAction(@NonNull SmartspaceTapAction subCardAction) {
+            mSubCardAction = subCardAction;
+            return this;
+        }
+
+        /**
+         * Builds a new SmartspaceSubCardUiTemplateData instance.
+         */
+        @NonNull
+        public SmartspaceSubCardUiTemplateData build() {
+            return new SmartspaceSubCardUiTemplateData(getTemplateType(), getTitleText(),
+                    getTitleIcon(), getSubtitleText(), getSubTitleIcon(), getPrimaryTapAction(),
+                    getSupplementalSubtitleText(), getSupplementalSubtitleIcon(),
+                    getSupplementalSubtitleTapAction(), getSupplementalAlarmText(), mSubCardIcon,
+                    mSubCardText,
+                    mSubCardAction);
+        }
+    }
+}
diff --git a/core/java/android/app/smartspace/uitemplatedata/SmartspaceSubImageUiTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/SmartspaceSubImageUiTemplateData.java
new file mode 100644
index 0000000..c479993
--- /dev/null
+++ b/core/java/android/app/smartspace/uitemplatedata/SmartspaceSubImageUiTemplateData.java
@@ -0,0 +1,192 @@
+/*
+ * 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.app.smartspace.uitemplatedata;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.smartspace.SmartspaceTarget;
+import android.os.Parcel;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Holds all the relevant data needed to render a Smartspace card with the sub-image Ui Template.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceSubImageUiTemplateData extends SmartspaceDefaultUiTemplateData {
+
+    /** Texts are shown next to the image as a vertical list */
+    @NonNull
+    private final List<CharSequence> mSubImageTexts;
+
+    /** If multiple images are passed in, they will be rendered as GIF. */
+    @NonNull
+    private final List<SmartspaceIcon> mSubImages;
+
+    /** Tap action for the sub-image secondary card. */
+    @Nullable
+    private final SmartspaceTapAction mSubImageAction;
+
+    SmartspaceSubImageUiTemplateData(@NonNull Parcel in) {
+        super(in);
+        mSubImageTexts = Arrays.asList(in.readCharSequenceArray());
+        mSubImages = in.createTypedArrayList(SmartspaceIcon.CREATOR);
+        mSubImageAction = in.readTypedObject(SmartspaceTapAction.CREATOR);
+    }
+
+    private SmartspaceSubImageUiTemplateData(@SmartspaceTarget.UiTemplateType int templateType,
+            @Nullable CharSequence titleText,
+            @Nullable SmartspaceIcon titleIcon,
+            @Nullable CharSequence subtitleText,
+            @Nullable SmartspaceIcon subTitleIcon,
+            @Nullable SmartspaceTapAction primaryTapAction,
+            @Nullable CharSequence supplementalSubtitleText,
+            @Nullable SmartspaceIcon supplementalSubtitleIcon,
+            @Nullable SmartspaceTapAction supplementalSubtitleTapAction,
+            @Nullable CharSequence supplementalAlarmText,
+            @NonNull List<CharSequence> subImageTexts,
+            @NonNull List<SmartspaceIcon> subImages,
+            @Nullable SmartspaceTapAction subImageAction) {
+        super(templateType, titleText, titleIcon, subtitleText, subTitleIcon, primaryTapAction,
+                supplementalSubtitleText, supplementalSubtitleIcon, supplementalSubtitleTapAction,
+                supplementalAlarmText);
+        mSubImageTexts = subImageTexts;
+        mSubImages = subImages;
+        mSubImageAction = subImageAction;
+    }
+
+    @NonNull
+    public List<CharSequence> getSubImageTexts() {
+        return mSubImageTexts;
+    }
+
+    @NonNull
+    public List<SmartspaceIcon> getSubImages() {
+        return mSubImages;
+    }
+
+    @Nullable
+    public SmartspaceTapAction getSubImageAction() {
+        return mSubImageAction;
+    }
+
+    /**
+     * @see Parcelable.Creator
+     */
+    @NonNull
+    public static final Creator<SmartspaceSubImageUiTemplateData> CREATOR =
+            new Creator<SmartspaceSubImageUiTemplateData>() {
+                @Override
+                public SmartspaceSubImageUiTemplateData createFromParcel(Parcel in) {
+                    return new SmartspaceSubImageUiTemplateData(in);
+                }
+
+                @Override
+                public SmartspaceSubImageUiTemplateData[] newArray(int size) {
+                    return new SmartspaceSubImageUiTemplateData[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        super.writeToParcel(out, flags);
+        out.writeCharSequenceList(new ArrayList<>(mSubImageTexts));
+        out.writeTypedList(mSubImages);
+        out.writeTypedObject(mSubImageAction, flags);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof SmartspaceSubImageUiTemplateData)) return false;
+        if (!super.equals(o)) return false;
+        SmartspaceSubImageUiTemplateData that = (SmartspaceSubImageUiTemplateData) o;
+        return Objects.equals(mSubImageTexts, that.mSubImageTexts)
+                && Objects.equals(mSubImages, that.mSubImages) && Objects.equals(
+                mSubImageAction, that.mSubImageAction);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(super.hashCode(), mSubImageTexts, mSubImages, mSubImageAction);
+    }
+
+    @Override
+    public String toString() {
+        return super.toString() + " + SmartspaceSubImageUiTemplateData{"
+                + "mSubImageTexts=" + mSubImageTexts
+                + ", mSubImages=" + mSubImages
+                + ", mSubImageAction=" + mSubImageAction
+                + '}';
+    }
+
+    /**
+     * A builder for {@link SmartspaceSubImageUiTemplateData} object.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final class Builder extends SmartspaceDefaultUiTemplateData.Builder {
+
+        private final List<CharSequence> mSubImageTexts;
+        private final List<SmartspaceIcon> mSubImages;
+        private SmartspaceTapAction mSubImageAction;
+
+        /**
+         * A builder for {@link SmartspaceSubImageUiTemplateData}.
+         */
+        public Builder(@NonNull List<CharSequence> subImageTexts,
+                @NonNull List<SmartspaceIcon> subImages) {
+            super(SmartspaceTarget.UI_TEMPLATE_SUB_IMAGE);
+            mSubImageTexts = Objects.requireNonNull(subImageTexts);
+            mSubImages = Objects.requireNonNull(subImages);
+        }
+
+        /**
+         * Sets the card tap action.
+         */
+        @NonNull
+        public Builder setCarouselAction(@NonNull SmartspaceTapAction subImageAction) {
+            mSubImageAction = subImageAction;
+            return this;
+        }
+
+        /**
+         * Builds a new SmartspaceSubImageUiTemplateData instance.
+         */
+        @NonNull
+        public SmartspaceSubImageUiTemplateData build() {
+            return new SmartspaceSubImageUiTemplateData(getTemplateType(), getTitleText(),
+                    getTitleIcon(), getSubtitleText(), getSubTitleIcon(), getPrimaryTapAction(),
+                    getSupplementalSubtitleText(), getSupplementalSubtitleIcon(),
+                    getSupplementalSubtitleTapAction(), getSupplementalAlarmText(), mSubImageTexts,
+                    mSubImages,
+                    mSubImageAction);
+        }
+    }
+}
diff --git a/core/java/android/app/smartspace/uitemplatedata/SmartspaceSubListUiTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/SmartspaceSubListUiTemplateData.java
new file mode 100644
index 0000000..b5d9645
--- /dev/null
+++ b/core/java/android/app/smartspace/uitemplatedata/SmartspaceSubListUiTemplateData.java
@@ -0,0 +1,196 @@
+/*
+ * 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.app.smartspace.uitemplatedata;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.smartspace.SmartspaceTarget;
+import android.os.Parcel;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Holds all the relevant data needed to render a Smartspace card with the sub-list Ui Template.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceSubListUiTemplateData extends SmartspaceDefaultUiTemplateData {
+
+    @Nullable
+    private final SmartspaceIcon mSubListIcon;
+    @NonNull
+    private final List<CharSequence> mSubListTexts;
+
+    /** Tap action for the sub-list secondary card. */
+    @Nullable
+    private final SmartspaceTapAction mSubListAction;
+
+    SmartspaceSubListUiTemplateData(@NonNull Parcel in) {
+        super(in);
+        mSubListIcon = in.readTypedObject(SmartspaceIcon.CREATOR);
+        mSubListTexts = Arrays.asList(in.readCharSequenceArray());
+        mSubListAction = in.readTypedObject(SmartspaceTapAction.CREATOR);
+    }
+
+    private SmartspaceSubListUiTemplateData(@SmartspaceTarget.UiTemplateType int templateType,
+            @Nullable CharSequence titleText,
+            @Nullable SmartspaceIcon titleIcon,
+            @Nullable CharSequence subtitleText,
+            @Nullable SmartspaceIcon subTitleIcon,
+            @Nullable SmartspaceTapAction primaryTapAction,
+            @Nullable CharSequence supplementalSubtitleText,
+            @Nullable SmartspaceIcon supplementalSubtitleIcon,
+            @Nullable SmartspaceTapAction supplementalSubtitleTapAction,
+            @Nullable CharSequence supplementalAlarmText,
+            @Nullable SmartspaceIcon subListIcon,
+            @NonNull List<CharSequence> subListTexts,
+            @Nullable SmartspaceTapAction subListAction) {
+        super(templateType, titleText, titleIcon, subtitleText, subTitleIcon, primaryTapAction,
+                supplementalSubtitleText, supplementalSubtitleIcon, supplementalSubtitleTapAction,
+                supplementalAlarmText);
+        mSubListIcon = subListIcon;
+        mSubListTexts = subListTexts;
+        mSubListAction = subListAction;
+    }
+
+    @Nullable
+    public SmartspaceIcon getSubListIcon() {
+        return mSubListIcon;
+    }
+
+    @NonNull
+    public List<CharSequence> getSubListTexts() {
+        return mSubListTexts;
+    }
+
+    @Nullable
+    public SmartspaceTapAction getSubListAction() {
+        return mSubListAction;
+    }
+
+    /**
+     * @see Parcelable.Creator
+     */
+    @NonNull
+    public static final Creator<SmartspaceSubListUiTemplateData> CREATOR =
+            new Creator<SmartspaceSubListUiTemplateData>() {
+                @Override
+                public SmartspaceSubListUiTemplateData createFromParcel(Parcel in) {
+                    return new SmartspaceSubListUiTemplateData(in);
+                }
+
+                @Override
+                public SmartspaceSubListUiTemplateData[] newArray(int size) {
+                    return new SmartspaceSubListUiTemplateData[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        super.writeToParcel(out, flags);
+        out.writeTypedObject(mSubListIcon, flags);
+        out.writeCharSequenceList(new ArrayList<>(mSubListTexts));
+        out.writeTypedObject(mSubListAction, flags);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof SmartspaceSubListUiTemplateData)) return false;
+        if (!super.equals(o)) return false;
+        SmartspaceSubListUiTemplateData that = (SmartspaceSubListUiTemplateData) o;
+        return Objects.equals(mSubListIcon, that.mSubListIcon) && Objects.equals(
+                mSubListTexts, that.mSubListTexts) && Objects.equals(mSubListAction,
+                that.mSubListAction);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(super.hashCode(), mSubListIcon, mSubListTexts, mSubListAction);
+    }
+
+    @Override
+    public String toString() {
+        return super.toString() + " + SmartspaceSubListUiTemplateData{"
+                + "mSubListIcon=" + mSubListIcon
+                + ", mSubListTexts=" + mSubListTexts
+                + ", mSubListAction=" + mSubListAction
+                + '}';
+    }
+
+    /**
+     * A builder for {@link SmartspaceSubListUiTemplateData} object.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final class Builder extends SmartspaceDefaultUiTemplateData.Builder {
+
+        private SmartspaceIcon mSubListIcon;
+        private final List<CharSequence> mSubListTexts;
+        private SmartspaceTapAction mSubListAction;
+
+        /**
+         * A builder for {@link SmartspaceSubListUiTemplateData}.
+         */
+        public Builder(@NonNull List<CharSequence> subListTexts) {
+            super(SmartspaceTarget.UI_TEMPLATE_SUB_LIST);
+            mSubListTexts = Objects.requireNonNull(subListTexts);
+        }
+
+        /**
+         * Sets the sub-list card icon.
+         */
+        @NonNull
+        public Builder setSubListIcon(@NonNull SmartspaceIcon subListIcon) {
+            mSubListIcon = subListIcon;
+            return this;
+        }
+
+        /**
+         * Sets the card tap action.
+         */
+        @NonNull
+        public Builder setCarouselAction(@NonNull SmartspaceTapAction subListAction) {
+            mSubListAction = subListAction;
+            return this;
+        }
+
+        /**
+         * Builds a new SmartspaceSubListUiTemplateData instance.
+         */
+        @NonNull
+        public SmartspaceSubListUiTemplateData build() {
+            return new SmartspaceSubListUiTemplateData(getTemplateType(), getTitleText(),
+                    getTitleIcon(), getSubtitleText(), getSubTitleIcon(), getPrimaryTapAction(),
+                    getSupplementalSubtitleText(), getSupplementalSubtitleIcon(),
+                    getSupplementalSubtitleTapAction(), getSupplementalAlarmText(), mSubListIcon,
+                    mSubListTexts,
+                    mSubListAction);
+        }
+    }
+}
diff --git a/core/java/android/app/smartspace/uitemplatedata/SmartspaceTapAction.java b/core/java/android/app/smartspace/uitemplatedata/SmartspaceTapAction.java
new file mode 100644
index 0000000..27d8e5f
--- /dev/null
+++ b/core/java/android/app/smartspace/uitemplatedata/SmartspaceTapAction.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.smartspace.uitemplatedata;
+
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.app.PendingIntent;
+import android.app.smartspace.SmartspaceUtils;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.text.TextUtils;
+
+import java.util.Objects;
+
+/**
+ * A {@link SmartspaceTapAction} represents an action which can be taken by a user by tapping on
+ * either the title, the subtitle or on the icon. Supported instances are Intents and
+ * PendingIntents. These actions can be called from another process or within the client process.
+ *
+ * Clients can also receive ShorcutInfos in the extras bundle.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceTapAction implements Parcelable {
+
+    /** A unique Id of this {@link SmartspaceTapAction}. */
+    @Nullable
+    private final CharSequence mId;
+
+    @Nullable
+    private final Intent mIntent;
+
+    @Nullable
+    private final PendingIntent mPendingIntent;
+
+    @Nullable
+    private final UserHandle mUserHandle;
+
+    @Nullable
+    private Bundle mExtras;
+
+    SmartspaceTapAction(@NonNull Parcel in) {
+        mId = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+        mIntent = in.readTypedObject(Intent.CREATOR);
+        mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
+        mUserHandle = in.readTypedObject(UserHandle.CREATOR);
+        mExtras = in.readBundle();
+    }
+
+    private SmartspaceTapAction(@Nullable CharSequence id, @Nullable Intent intent,
+            @Nullable PendingIntent pendingIntent, @Nullable UserHandle userHandle,
+            @Nullable Bundle extras) {
+        mId = id;
+        mIntent = intent;
+        mPendingIntent = pendingIntent;
+        mUserHandle = userHandle;
+        mExtras = extras;
+    }
+
+    @Nullable
+    public CharSequence getId() {
+        return mId;
+    }
+
+    @SuppressLint("IntentBuilderName")
+    @Nullable
+    public Intent getIntent() {
+        return mIntent;
+    }
+
+    @Nullable
+    public PendingIntent getPendingIntent() {
+        return mPendingIntent;
+    }
+
+    @Nullable
+    public UserHandle getUserHandle() {
+        return mUserHandle;
+    }
+
+    @Nullable
+    @SuppressLint("NullableCollection")
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        TextUtils.writeToParcel(mId, out, flags);
+        out.writeTypedObject(mIntent, flags);
+        out.writeTypedObject(mPendingIntent, flags);
+        out.writeTypedObject(mUserHandle, flags);
+        out.writeBundle(mExtras);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @NonNull
+    public static final Creator<SmartspaceTapAction> CREATOR = new Creator<SmartspaceTapAction>() {
+        @Override
+        public SmartspaceTapAction createFromParcel(Parcel in) {
+            return new SmartspaceTapAction(in);
+        }
+
+        @Override
+        public SmartspaceTapAction[] newArray(int size) {
+            return new SmartspaceTapAction[size];
+        }
+    };
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof SmartspaceTapAction)) return false;
+        SmartspaceTapAction that = (SmartspaceTapAction) o;
+        return SmartspaceUtils.isEqual(mId, that.mId);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mId);
+    }
+
+    @Override
+    public String toString() {
+        return "SmartspaceTapAction{"
+                + "mId=" + mId
+                + "mIntent=" + mIntent
+                + ", mPendingIntent=" + mPendingIntent
+                + ", mUserHandle=" + mUserHandle
+                + ", mExtras=" + mExtras
+                + '}';
+    }
+
+    /**
+     * A builder for {@link SmartspaceTapAction} object.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final class Builder {
+
+        private CharSequence mId;
+        private Intent mIntent;
+        private PendingIntent mPendingIntent;
+        private UserHandle mUserHandle;
+        private Bundle mExtras;
+
+        /**
+         * A builder for {@link SmartspaceTapAction}.
+         *
+         * @param id A unique Id of this {@link SmartspaceTapAction}.
+         */
+        public Builder(@NonNull CharSequence id) {
+            mId = Objects.requireNonNull(id);
+        }
+
+        /**
+         * Sets the action intent.
+         */
+        @NonNull
+        public Builder setIntent(@NonNull Intent intent) {
+            mIntent = intent;
+            return this;
+        }
+
+        /**
+         * Sets the pending intent.
+         */
+        @NonNull
+        public Builder setPendingIntent(@NonNull PendingIntent pendingIntent) {
+            mPendingIntent = pendingIntent;
+            return this;
+        }
+
+        /**
+         * Sets the user handle.
+         */
+        @NonNull
+        @SuppressLint("UserHandleName")
+        public Builder setUserHandle(@Nullable UserHandle userHandle) {
+            mUserHandle = userHandle;
+            return this;
+        }
+
+        /**
+         * Sets the extras.
+         */
+        @NonNull
+        public Builder setExtras(@NonNull Bundle extras) {
+            mExtras = extras;
+            return this;
+        }
+
+        /**
+         * Builds a new SmartspaceTapAction instance.
+         *
+         * @throws IllegalStateException if the tap action is empty.
+         */
+        @NonNull
+        public SmartspaceTapAction build() {
+            if (mIntent == null && mPendingIntent == null && mExtras == null) {
+                throw new IllegalStateException("Please assign at least 1 valid tap field");
+            }
+            return new SmartspaceTapAction(mId, mIntent, mPendingIntent, mUserHandle, mExtras);
+        }
+    }
+}
diff --git a/core/java/android/app/trust/ITrustManager.aidl b/core/java/android/app/trust/ITrustManager.aidl
index edabccf..7956a35 100644
--- a/core/java/android/app/trust/ITrustManager.aidl
+++ b/core/java/android/app/trust/ITrustManager.aidl
@@ -17,6 +17,7 @@
 package android.app.trust;
 
 import android.app.trust.ITrustListener;
+import android.content.ComponentName;
 import android.hardware.biometrics.BiometricSourceType;
 
 /**
@@ -29,6 +30,7 @@
     void reportUserRequestedUnlock(int userId);
     void reportUnlockLockout(int timeoutMs, int userId);
     void reportEnabledTrustAgentsChanged(int userId);
+    void enableTrustAgentForUserForTest(in ComponentName componentName, int userId);
     void registerTrustListener(in ITrustListener trustListener);
     void unregisterTrustListener(in ITrustListener trustListener);
     void reportKeyguardShowingChanged();
diff --git a/core/java/android/app/trust/TrustManager.java b/core/java/android/app/trust/TrustManager.java
index 70b7de0..fba2d3e 100644
--- a/core/java/android/app/trust/TrustManager.java
+++ b/core/java/android/app/trust/TrustManager.java
@@ -16,10 +16,14 @@
 
 package android.app.trust;
 
-import android.Manifest;
+import static android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE;
+
+import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
+import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
 import android.content.Context;
 import android.hardware.biometrics.BiometricSourceType;
 import android.os.Handler;
@@ -33,9 +37,17 @@
 import java.util.List;
 
 /**
- * See {@link com.android.server.trust.TrustManagerService}
+ * Interface to the system service managing trust.
+ *
+ * <p>This class is for internal use only. This class is marked {@code @TestApi} to
+ * enable testing the trust system including {@link android.service.trust.TrustAgentService}.
+ * Methods which are currently not used in tests are marked @hide.
+ *
+ * @see com.android.server.trust.TrustManagerService
+ *
  * @hide
  */
+@TestApi
 @SystemService(Context.TRUST_SERVICE)
 public class TrustManager {
 
@@ -51,7 +63,8 @@
     private final ITrustManager mService;
     private final ArrayMap<TrustListener, ITrustListener> mTrustListeners;
 
-    public TrustManager(IBinder b) {
+    /** @hide */
+    public TrustManager(@NonNull IBinder b) {
         mService = ITrustManager.Stub.asInterface(b);
         mTrustListeners = new ArrayMap<TrustListener, ITrustListener>();
     }
@@ -62,8 +75,10 @@
      *
      * @param userId The id for the user to be locked/unlocked.
      * @param locked The value for that user's locked state.
+     *
+     * @hide
      */
-    @RequiresPermission(Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE)
+    @RequiresPermission(ACCESS_KEYGUARD_SECURE_STORAGE)
     public void setDeviceLockedForUser(int userId, boolean locked) {
         try {
             mService.setDeviceLockedForUser(userId, locked);
@@ -78,8 +93,11 @@
      * @param successful if true, the unlock attempt was successful.
      *
      * Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission.
+     *
+     * @hide
      */
     @UnsupportedAppUsage
+    @RequiresPermission(ACCESS_KEYGUARD_SECURE_STORAGE)
     public void reportUnlockAttempt(boolean successful, int userId) {
         try {
             mService.reportUnlockAttempt(successful, userId);
@@ -93,6 +111,7 @@
      *
      * Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission.
      */
+    @RequiresPermission(ACCESS_KEYGUARD_SECURE_STORAGE)
     public void reportUserRequestedUnlock(int userId) {
         try {
             mService.reportUserRequestedUnlock(userId);
@@ -112,7 +131,10 @@
      *    attempt to unlock the device again.
      *
      * Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission.
+     *
+     * @hide
      */
+    @RequiresPermission(ACCESS_KEYGUARD_SECURE_STORAGE)
     public void reportUnlockLockout(int timeoutMs, int userId) {
         try {
             mService.reportUnlockLockout(timeoutMs, userId);
@@ -125,7 +147,10 @@
      * Reports that the list of enabled trust agents changed for user {@param userId}.
      *
      * Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission.
+     *
+     * @hide
      */
+    @RequiresPermission(ACCESS_KEYGUARD_SECURE_STORAGE)
     public void reportEnabledTrustAgentsChanged(int userId) {
         try {
             mService.reportEnabledTrustAgentsChanged(userId);
@@ -135,10 +160,33 @@
     }
 
     /**
+     * Enables a trust agent.
+     *
+     * <p>The agent is specified by {@code componentName} and must be a subclass of
+     * {@link android.service.trust.TrustAgentService} and otherwise meet the requirements
+     * to be a trust agent.
+     *
+     * <p>This method can only be used in tests.
+     *
+     * @param componentName the trust agent to enable
+     */
+    @RequiresPermission(ACCESS_KEYGUARD_SECURE_STORAGE)
+    public void enableTrustAgentForUserForTest(@NonNull ComponentName componentName, int userId) {
+        try {
+            mService.enableTrustAgentForUserForTest(componentName, userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Reports that the visibility of the keyguard has changed.
      *
      * Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission.
+     *
+     * @hide
      */
+    @RequiresPermission(ACCESS_KEYGUARD_SECURE_STORAGE)
     public void reportKeyguardShowingChanged() {
         try {
             mService.reportKeyguardShowingChanged();
@@ -151,7 +199,10 @@
      * Registers a listener for trust events.
      *
      * Requires the {@link android.Manifest.permission#TRUST_LISTENER} permission.
+     *
+     * @hide
      */
+    @RequiresPermission(android.Manifest.permission.TRUST_LISTENER)
     public void registerTrustListener(final TrustListener trustListener) {
         try {
             ITrustListener.Stub iTrustListener = new ITrustListener.Stub() {
@@ -192,7 +243,10 @@
      * Unregisters a listener for trust events.
      *
      * Requires the {@link android.Manifest.permission#TRUST_LISTENER} permission.
+     *
+     * @hide
      */
+    @RequiresPermission(android.Manifest.permission.TRUST_LISTENER)
     public void unregisterTrustListener(final TrustListener trustListener) {
         ITrustListener iTrustListener = mTrustListeners.remove(trustListener);
         if (iTrustListener != null) {
@@ -207,6 +261,8 @@
     /**
      * @return whether {@param userId} has enabled and configured trust agents. Ignores short-term
      * unavailability of trust due to {@link LockPatternUtils.StrongAuthTracker}.
+     *
+     * @hide
      */
     @RequiresPermission(android.Manifest.permission.TRUST_LISTENER)
     public boolean isTrustUsuallyManaged(int userId) {
@@ -223,8 +279,10 @@
      * can be skipped.
      *
      * @param userId
+     *
+     * @hide
      */
-    @RequiresPermission(Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE)
+    @RequiresPermission(ACCESS_KEYGUARD_SECURE_STORAGE)
     public void unlockedByBiometricForUser(int userId, BiometricSourceType source) {
         try {
             mService.unlockedByBiometricForUser(userId, source);
@@ -235,8 +293,10 @@
 
     /**
      * Clears authentication by the specified biometric type for all users.
+     *
+     * @hide
      */
-    @RequiresPermission(Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE)
+    @RequiresPermission(ACCESS_KEYGUARD_SECURE_STORAGE)
     public void clearAllBiometricRecognized(BiometricSourceType source, int unlockedUser) {
         try {
             mService.clearAllBiometricRecognized(source, unlockedUser);
@@ -264,6 +324,7 @@
         }
     };
 
+    /** @hide */
     public interface TrustListener {
 
         /**
diff --git a/core/java/android/apphibernation/AppHibernationManager.java b/core/java/android/apphibernation/AppHibernationManager.java
index a36da88..6e3bbcc 100644
--- a/core/java/android/apphibernation/AppHibernationManager.java
+++ b/core/java/android/apphibernation/AppHibernationManager.java
@@ -24,7 +24,10 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 
+import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * This class provides an API surface for system apps to manipulate the app hibernation
@@ -129,4 +132,38 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Returns the stats from app hibernation for each package provided.
+     *
+     * @param packageNames the set of packages to return stats for
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(value = android.Manifest.permission.MANAGE_APP_HIBERNATION)
+    public @NonNull Map<String, HibernationStats> getHibernationStatsForUser(
+            @NonNull Set<String> packageNames) {
+        try {
+            return mIAppHibernationService.getHibernationStatsForUser(
+                    new ArrayList(packageNames), mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the stats from app hibernation for all packages for the user
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(value = android.Manifest.permission.MANAGE_APP_HIBERNATION)
+    public @NonNull Map<String, HibernationStats> getHibernationStatsForUser() {
+        try {
+            return mIAppHibernationService.getHibernationStatsForUser(
+                    null /* packageNames */, mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/apphibernation/HibernationStats.aidl b/core/java/android/apphibernation/HibernationStats.aidl
new file mode 100644
index 0000000..a92b903
--- /dev/null
+++ b/core/java/android/apphibernation/HibernationStats.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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.apphibernation;
+
+parcelable HibernationStats;
\ No newline at end of file
diff --git a/core/java/android/apphibernation/HibernationStats.java b/core/java/android/apphibernation/HibernationStats.java
new file mode 100644
index 0000000..2c4db82
--- /dev/null
+++ b/core/java/android/apphibernation/HibernationStats.java
@@ -0,0 +1,70 @@
+/*
+ * 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.apphibernation;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Stats for a hibernating package.
+ * @hide
+ */
+@SystemApi
+public final class HibernationStats implements Parcelable {
+    private final long mDiskBytesSaved;
+
+    /** @hide */
+    public HibernationStats(long diskBytesSaved) {
+        mDiskBytesSaved = diskBytesSaved;
+    }
+
+    private HibernationStats(@NonNull Parcel in) {
+        mDiskBytesSaved = in.readLong();
+    }
+
+    /**
+     * Get the disk storage saved from hibernation in bytes.
+     */
+    public long getDiskBytesSaved() {
+        return mDiskBytesSaved;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeLong(mDiskBytesSaved);
+    }
+
+    public static final @NonNull Creator<HibernationStats> CREATOR =
+            new Creator<HibernationStats>() {
+        @Override
+        public HibernationStats createFromParcel(Parcel in) {
+            return new HibernationStats(in);
+        }
+
+        @Override
+        public HibernationStats[] newArray(int size) {
+            return new HibernationStats[size];
+        }
+    };
+}
diff --git a/core/java/android/apphibernation/IAppHibernationService.aidl b/core/java/android/apphibernation/IAppHibernationService.aidl
index afdb3fe..11bb6b5 100644
--- a/core/java/android/apphibernation/IAppHibernationService.aidl
+++ b/core/java/android/apphibernation/IAppHibernationService.aidl
@@ -16,6 +16,8 @@
 
 package android.apphibernation;
 
+import android.apphibernation.HibernationStats;
+
 /**
  * Binder interface to communicate with AppHibernationService.
  * @hide
@@ -26,4 +28,6 @@
     boolean isHibernatingGlobally(String packageName);
     void setHibernatingGlobally(String packageName, boolean isHibernating);
     List<String> getHibernatingPackagesForUser(int userId);
+    Map<String, HibernationStats> getHibernationStatsForUser(in List<String> packageNames,
+            int userId);
 }
\ No newline at end of file
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index 2ddfeb4..1d0f7c0 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -22,6 +22,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.content.ComponentName;
 import android.os.Parcel;
@@ -106,11 +107,13 @@
     }
 
     /**
-     * Returns the set of activities allowed to be streamed, or {@code null} if this is not set.
+     * Returns the set of activities allowed to be streamed, or {@code null} if all activities are
+     * allowed, except the ones explicitly blocked.
      *
      * @see Builder#setAllowedActivities(Set)
-     * @hide  // TODO(b/194949534): Unhide this API
      */
+    // Null and empty have different semantics - Null allows all activities to be streamed
+    @SuppressLint("NullableCollection")
     @Nullable
     public Set<ComponentName> getAllowedActivities() {
         if (mAllowedActivities == null) {
@@ -120,12 +123,13 @@
     }
 
     /**
-     * Returns the set of activities that are blocked from streaming, or {@code null} if this is not
-     * set.
+     * Returns the set of activities that are blocked from streaming, or {@code null} to indicate
+     * that all activities in {@link #getAllowedActivities} are allowed.
      *
      * @see Builder#setBlockedActivities(Set)
-     * @hide  // TODO(b/194949534): Unhide this API
      */
+    // Allowing null to enforce that at most one of allowed / blocked activities can be non-null
+    @SuppressLint("NullableCollection")
     @Nullable
     public Set<ComponentName> getBlockedActivities() {
         if (mBlockedActivities == null) {
@@ -255,8 +259,10 @@
          *
          * @param allowedActivities A set of activity {@link ComponentName} allowed to be launched
          *   in the virtual device.
-         * @hide  // TODO(b/194949534): Unhide this API
          */
+        // Null and empty have different semantics - Null allows all activities to be streamed
+        @SuppressLint("NullableCollection")
+        @NonNull
         public Builder setAllowedActivities(@Nullable Set<ComponentName> allowedActivities) {
             if (mBlockedActivities != null && allowedActivities != null) {
                 throw new IllegalArgumentException(
@@ -279,8 +285,10 @@
          *
          * @param blockedActivities A set of {@link ComponentName} to be blocked launching from
          *   virtual device.
-         * @hide  // TODO(b/194949534): Unhide this API
          */
+        // Allowing null to enforce that at most one of allowed / blocked activities can be non-null
+        @SuppressLint("NullableCollection")
+        @NonNull
         public Builder setBlockedActivities(@Nullable Set<ComponentName> blockedActivities) {
             if (mAllowedActivities != null && blockedActivities != null) {
                 throw new IllegalArgumentException(
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 4b4e008..0b8a8a2 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4987,10 +4987,8 @@
      * @hide
      * @see #getSystemService(String)
      */
-    // TODO(216507592): Change cloudsearch_service to cloudsearch.
     @SystemApi
-    @SuppressLint("ServiceName")
-    public static final String CLOUDSEARCH_SERVICE = "cloudsearch_service";
+    public static final String CLOUDSEARCH_SERVICE = "cloudsearch";
 
     /**
      * Use with {@link #getSystemService(String)} to access the
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index a5d97f9..bb88486 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -363,6 +363,9 @@
     /**
      * The group this permission is a part of, as per
      * {@link android.R.attr#permissionGroup}.
+     * <p>
+     * The actual grouping of platform-defined runtime permissions is subject to change and can be
+     * queried with {@link PackageManager#getGroupOfPlatformPermission}.
      */
     public @Nullable String group;
 
diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java
index f13c795..52bba14 100644
--- a/core/java/android/database/CursorWindow.java
+++ b/core/java/android/database/CursorWindow.java
@@ -131,11 +131,16 @@
      *
      * @param name The name of the cursor window, or null if none.
      * @param windowSizeBytes Size of cursor window in bytes.
+     * @throws IllegalArgumentException if {@code windowSizeBytes} is less than 0
+     * @throws AssertionError if created window pointer is 0
      * <p><strong>Note:</strong> Memory is dynamically allocated as data rows are added to the
      * window. Depending on the amount of data stored, the actual amount of memory allocated can be
      * lower than specified size, but cannot exceed it.
      */
     public CursorWindow(String name, @BytesLong long windowSizeBytes) {
+        if (windowSizeBytes < 0) {
+            throw new IllegalArgumentException("Window size cannot be less than 0");
+        }
         mStartPos = 0;
         mName = name != null && name.length() != 0 ? name : "<unnamed>";
         mWindowPtr = nativeCreate(mName, (int) windowSizeBytes);
diff --git a/core/java/android/database/CursorWindowAllocationException.java b/core/java/android/database/CursorWindowAllocationException.java
index 2e3227d..5315c8b 100644
--- a/core/java/android/database/CursorWindowAllocationException.java
+++ b/core/java/android/database/CursorWindowAllocationException.java
@@ -16,14 +16,14 @@
 
 package android.database;
 
+import android.annotation.NonNull;
+
 /**
  * This exception is thrown when a CursorWindow couldn't be allocated,
  * most probably due to memory not being available.
- *
- * @hide
  */
 public class CursorWindowAllocationException extends RuntimeException {
-    public CursorWindowAllocationException(String description) {
+    public CursorWindowAllocationException(@NonNull String description) {
         super(description);
     }
 }
diff --git a/core/java/android/hardware/biometrics/IBiometricContextListener.aidl b/core/java/android/hardware/biometrics/IBiometricContextListener.aidl
new file mode 100644
index 0000000..55cab52
--- /dev/null
+++ b/core/java/android/hardware/biometrics/IBiometricContextListener.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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.hardware.biometrics;
+
+/**
+ * A secondary communication channel from AuthController back to BiometricService for
+ * events that are not associated with an autentication session. See
+ * {@link IBiometricSysuiReceiver} for events associated with a session.
+ *
+ * @hide
+ */
+oneway interface IBiometricContextListener {
+    void onDozeChanged(boolean isDozing);
+}
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 0304815..27403ec 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -122,9 +122,9 @@
     void removePortAssociation(in String inputPort);
 
     // Add a runtime association between the input device and display.
-    void addUniqueIdAssociation(in String inputDeviceName, in String displayUniqueId);
+    void addUniqueIdAssociation(in String inputPort, in String displayUniqueId);
     // Remove the runtime association between the input device and display.
-    void removeUniqueIdAssociation(in String inputDeviceName);
+    void removeUniqueIdAssociation(in String inputPort);
 
     InputSensorInfo[] getSensorList(int deviceId);
 
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index cbc8373..979e9dd 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1359,19 +1359,18 @@
     }
 
     /**
-     * Add a runtime association between the input device name and display, by unique id. Input
-     * device names are expected to be unique.
-     * @param inputDeviceName The name of the input device.
+     * Add a runtime association between the input port and display, by unique id. Input ports are
+     * expected to be unique.
+     * @param inputPort The port of the input device.
      * @param displayUniqueId The unique id of the associated display.
      * <p>
      * Requires {@link android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY}.
      * </p>
      * @hide
      */
-    public void addUniqueIdAssociation(@NonNull String inputDeviceName,
-            @NonNull String displayUniqueId) {
+    public void addUniqueIdAssociation(@NonNull String inputPort, @NonNull String displayUniqueId) {
         try {
-            mIm.addUniqueIdAssociation(inputDeviceName, displayUniqueId);
+            mIm.addUniqueIdAssociation(inputPort, displayUniqueId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1379,15 +1378,15 @@
 
     /**
      * Removes a runtime association between the input device and display.
-     * @param inputDeviceName The name of the input device.
+     * @param inputPort The port of the input device.
      * <p>
      * Requires {@link android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY}.
      * </p>
      * @hide
      */
-    public void removeUniqueIdAssociation(@NonNull String inputDeviceName) {
+    public void removeUniqueIdAssociation(@NonNull String inputPort) {
         try {
-            mIm.removeUniqueIdAssociation(inputDeviceName);
+            mIm.removeUniqueIdAssociation(inputPort);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index 41642e7..af57f79 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -70,6 +70,7 @@
     private static final int DO_SET_INPUT_CONTEXT = 20;
     private static final int DO_UNSET_INPUT_CONTEXT = 30;
     private static final int DO_START_INPUT = 32;
+    private static final int DO_ON_SHOULD_SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN_CHANGED = 35;
     private static final int DO_CREATE_SESSION = 40;
     private static final int DO_SET_SESSION_ENABLED = 45;
     private static final int DO_SHOW_SOFT_INPUT = 60;
@@ -175,7 +176,7 @@
                 try {
                     inputMethod.initializeInternal((IBinder) args.arg1,
                             (IInputMethodPrivilegedOperations) args.arg2, msg.arg1,
-                            (boolean) args.arg3);
+                            (boolean) args.arg3, msg.arg2 != 0);
                 } finally {
                     args.recycle();
                 }
@@ -195,14 +196,22 @@
                 final EditorInfo info = (EditorInfo) args.arg3;
                 final CancellationGroup cancellationGroup = (CancellationGroup) args.arg4;
                 final boolean restarting = args.argi5 == 1;
+                final boolean shouldShowImeSwitcherWhenImeIsShown = args.argi6 != 0;
                 final InputConnection ic = inputContext != null
                         ? new RemoteInputConnection(mTarget, inputContext, cancellationGroup)
                         : null;
                 info.makeCompatible(mTargetSdkVersion);
-                inputMethod.dispatchStartInputWithToken(ic, info, restarting, startInputToken);
+                inputMethod.dispatchStartInputWithToken(ic, info, restarting, startInputToken,
+                        shouldShowImeSwitcherWhenImeIsShown);
                 args.recycle();
                 return;
             }
+            case DO_ON_SHOULD_SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN_CHANGED: {
+                final boolean shouldShowImeSwitcherWhenImeIsShown = msg.arg1 != 0;
+                inputMethod.onShouldShowImeSwitcherWhenImeIsShownChanged(
+                        shouldShowImeSwitcherWhenImeIsShown);
+                return;
+            }
             case DO_CREATE_SESSION: {
                 SomeArgs args = (SomeArgs)msg.obj;
                 inputMethod.createSession(new InputMethodSessionCallbackWrapper(
@@ -291,10 +300,11 @@
     @BinderThread
     @Override
     public void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps,
-            int configChanges, boolean stylusHwSupported) {
-        mCaller.executeOrSendMessage(
-                mCaller.obtainMessageIOOO(
-                        DO_INITIALIZE_INTERNAL, configChanges, token, privOps, stylusHwSupported));
+            int configChanges, boolean stylusHwSupported,
+            boolean shouldShowImeSwitcherWhenImeIsShown) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageIIOOO(DO_INITIALIZE_INTERNAL,
+                configChanges, shouldShowImeSwitcherWhenImeIsShown ? 1 : 0, token, privOps,
+                stylusHwSupported));
     }
 
     @BinderThread
@@ -334,13 +344,23 @@
     @BinderThread
     @Override
     public void startInput(IBinder startInputToken, IInputContext inputContext,
-            EditorInfo attribute, boolean restarting) {
+            EditorInfo attribute, boolean restarting, boolean shouldShowImeSwitcherWhenImeIsShown) {
         if (mCancellationGroup == null) {
             Log.e(TAG, "startInput must be called after bindInput.");
             mCancellationGroup = new CancellationGroup();
         }
         mCaller.executeOrSendMessage(mCaller.obtainMessageOOOOII(DO_START_INPUT, startInputToken,
-                inputContext, attribute, mCancellationGroup, restarting ? 1 : 0, 0 /* unused */));
+                inputContext, attribute, mCancellationGroup, restarting ? 1 : 0,
+                shouldShowImeSwitcherWhenImeIsShown ? 1 : 0));
+    }
+
+    @BinderThread
+    @Override
+    public void onShouldShowImeSwitcherWhenImeIsShownChanged(
+            boolean shouldShowImeSwitcherWhenImeIsShown) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageI(
+                DO_ON_SHOULD_SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN_CHANGED,
+                shouldShowImeSwitcherWhenImeIsShown ? 1 : 0));
     }
 
     @BinderThread
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 14f92fb..f55c415 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -658,7 +658,7 @@
         @Override
         public final void initializeInternal(@NonNull IBinder token,
                 IInputMethodPrivilegedOperations privilegedOperations, int configChanges,
-                boolean stylusHwSupported) {
+                boolean stylusHwSupported, boolean shouldShowImeSwitcherWhenImeIsShown) {
             if (mDestroyed) {
                 Log.i(TAG, "The InputMethodService has already onDestroyed()."
                     + "Ignore the initialization.");
@@ -671,6 +671,8 @@
             if (stylusHwSupported) {
                 mInkWindow = new InkWindow(mWindow.getContext());
             }
+            mNavigationBarController.setShouldShowImeSwitcherWhenImeIsShown(
+                    shouldShowImeSwitcherWhenImeIsShown);
             attachToken(token);
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
@@ -780,9 +782,10 @@
         @Override
         public final void dispatchStartInputWithToken(@Nullable InputConnection inputConnection,
                 @NonNull EditorInfo editorInfo, boolean restarting,
-                @NonNull IBinder startInputToken) {
+                @NonNull IBinder startInputToken, boolean shouldShowImeSwitcherWhenImeIsShown) {
             mPrivOps.reportStartInputAsync(startInputToken);
-
+            mNavigationBarController.setShouldShowImeSwitcherWhenImeIsShown(
+                    shouldShowImeSwitcherWhenImeIsShown);
             if (restarting) {
                 restartInput(inputConnection, editorInfo);
             } else {
@@ -796,6 +799,18 @@
          */
         @MainThread
         @Override
+        public void onShouldShowImeSwitcherWhenImeIsShownChanged(
+                boolean shouldShowImeSwitcherWhenImeIsShown) {
+            mNavigationBarController.setShouldShowImeSwitcherWhenImeIsShown(
+                    shouldShowImeSwitcherWhenImeIsShown);
+        }
+
+        /**
+         * {@inheritDoc}
+         * @hide
+         */
+        @MainThread
+        @Override
         public void hideSoftInputWithToken(int flags, ResultReceiver resultReceiver,
                 IBinder hideInputToken) {
             mSystemCallingHideSoftInput = true;
diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java
index 2484cf0..508172d 100644
--- a/core/java/android/inputmethodservice/NavigationBarController.java
+++ b/core/java/android/inputmethodservice/NavigationBarController.java
@@ -19,6 +19,7 @@
 import static android.content.Intent.ACTION_OVERLAY_CHANGED;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
 
+import android.animation.ValueAnimator;
 import android.annotation.FloatRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -44,6 +45,8 @@
 import android.view.WindowInsets;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowManagerPolicyConstants;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
 import android.widget.FrameLayout;
 
 import java.util.Objects;
@@ -71,6 +74,10 @@
         default void onDestroy() {
         }
 
+        default void setShouldShowImeSwitcherWhenImeIsShown(
+                boolean shouldShowImeSwitcherWhenImeIsShown) {
+        }
+
         default void onSystemBarAppearanceChanged(@Appearance int appearance) {
         }
 
@@ -106,6 +113,10 @@
         mImpl.onDestroy();
     }
 
+    void setShouldShowImeSwitcherWhenImeIsShown(boolean shouldShowImeSwitcherWhenImeIsShown) {
+        mImpl.setShouldShowImeSwitcherWhenImeIsShown(shouldShowImeSwitcherWhenImeIsShown);
+    }
+
     void onSystemBarAppearanceChanged(@Appearance int appearance) {
         mImpl.onSystemBarAppearanceChanged(appearance);
     }
@@ -115,6 +126,12 @@
     }
 
     private static final class Impl implements Callback {
+        private static final int DEFAULT_COLOR_ADAPT_TRANSITION_TIME = 1700;
+
+        // Copied from com.android.systemui.animation.Interpolators#LEGACY_DECELERATE
+        private static final Interpolator LEGACY_DECELERATE =
+                new PathInterpolator(0f, 0f, 0.2f, 1f);
+
         @NonNull
         private final InputMethodService mService;
 
@@ -130,9 +147,17 @@
         @Nullable
         private BroadcastReceiver mSystemOverlayChangedReceiver;
 
+        private boolean mShouldShowImeSwitcherWhenImeIsShown;
+
         @Appearance
         private int mAppearance;
 
+        @FloatRange(from = 0.0f, to = 1.0f)
+        private float mDarkIntensity;
+
+        @Nullable
+        private ValueAnimator mTintAnimator;
+
         Impl(@NonNull InputMethodService inputMethodService) {
             mService = inputMethodService;
         }
@@ -190,7 +215,9 @@
                     // TODO(b/213337792): Support InputMethodService#setBackDisposition().
                     // TODO(b/213337792): Set NAVIGATION_HINT_IME_SHOWN only when necessary.
                     final int hints = StatusBarManager.NAVIGATION_HINT_BACK_ALT
-                            | StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
+                            | (mShouldShowImeSwitcherWhenImeIsShown
+                                    ? StatusBarManager.NAVIGATION_HINT_IME_SHOWN
+                                    : 0);
                     navigationBarView.setNavigationIconHints(hints);
                 }
             } else {
@@ -368,6 +395,10 @@
             if (mDestroyed) {
                 return;
             }
+            if (mTintAnimator != null) {
+                mTintAnimator.cancel();
+                mTintAnimator = null;
+            }
             if (mSystemOverlayChangedReceiver != null) {
                 mService.unregisterReceiver(mSystemOverlayChangedReceiver);
                 mSystemOverlayChangedReceiver = null;
@@ -404,6 +435,31 @@
         }
 
         @Override
+        public void setShouldShowImeSwitcherWhenImeIsShown(
+                boolean shouldShowImeSwitcherWhenImeIsShown) {
+            if (mDestroyed) {
+                return;
+            }
+            if (mShouldShowImeSwitcherWhenImeIsShown == shouldShowImeSwitcherWhenImeIsShown) {
+                return;
+            }
+            mShouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherWhenImeIsShown;
+
+            if (mNavigationBarFrame == null) {
+                return;
+            }
+            final NavigationBarView navigationBarView =
+                    mNavigationBarFrame.findViewByPredicate(NavigationBarView.class::isInstance);
+            if (navigationBarView == null) {
+                return;
+            }
+            final int hints = StatusBarManager.NAVIGATION_HINT_BACK_ALT
+                    | (shouldShowImeSwitcherWhenImeIsShown
+                    ? StatusBarManager.NAVIGATION_HINT_IME_SHOWN : 0);
+            navigationBarView.setNavigationIconHints(hints);
+        }
+
+        @Override
         public void onSystemBarAppearanceChanged(@Appearance int appearance) {
             if (mDestroyed) {
                 return;
@@ -416,10 +472,24 @@
             }
 
             final float targetDarkIntensity = calculateTargetDarkIntensity(mAppearance);
-            setIconTintInternal(targetDarkIntensity);
+
+            if (mTintAnimator != null) {
+                mTintAnimator.cancel();
+            }
+            mTintAnimator = ValueAnimator.ofFloat(mDarkIntensity, targetDarkIntensity);
+            mTintAnimator.addUpdateListener(
+                    animation -> setIconTintInternal((Float) animation.getAnimatedValue()));
+            mTintAnimator.setDuration(DEFAULT_COLOR_ADAPT_TRANSITION_TIME);
+            mTintAnimator.setStartDelay(0);
+            mTintAnimator.setInterpolator(LEGACY_DECELERATE);
+            mTintAnimator.start();
         }
 
         private void setIconTintInternal(float darkIntensity) {
+            mDarkIntensity = darkIntensity;
+            if (mNavigationBarFrame == null) {
+                return;
+            }
             final NavigationBarView navigationBarView =
                     mNavigationBarFrame.findViewByPredicate(NavigationBarView.class::isInstance);
             if (navigationBarView == null) {
@@ -438,7 +508,9 @@
         public String toDebugString() {
             return "{mRenderGesturalNavButtons=" + mRenderGesturalNavButtons
                     + " mNavigationBarFrame=" + mNavigationBarFrame
+                    + " mShouldShowImeSwitcherWhenImeIsShown" + mShouldShowImeSwitcherWhenImeIsShown
                     + " mAppearance=0x" + Integer.toHexString(mAppearance)
+                    + " mDarkIntensity=" + mDarkIntensity
                     + "}";
         }
     }
diff --git a/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java b/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java
index 24c22a9..9772bde 100644
--- a/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java
+++ b/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java
@@ -16,9 +16,7 @@
 
 package android.net.netstats;
 
-import static android.app.usage.NetworkStatsManager.PREFIX_UID;
-import static android.app.usage.NetworkStatsManager.PREFIX_UID_TAG;
-import static android.app.usage.NetworkStatsManager.PREFIX_XT;
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
 import static android.net.ConnectivityManager.TYPE_MOBILE;
 import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
 import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
@@ -28,6 +26,7 @@
 import static android.net.NetworkStats.TAG_NONE;
 
 import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.net.NetworkIdentity;
 import android.net.NetworkStatsCollection;
 import android.net.NetworkStatsHistory;
@@ -54,12 +53,27 @@
 import java.util.Set;
 
 /**
- * Helper class to read old version of persistent network statistics, the implementation is
- * intended to be modified by OEM partners to accommodate their custom changes.
+ * Helper class to read old version of persistent network statistics.
+ *
+ * The implementation is intended to be modified by OEM partners to
+ * accommodate their custom changes.
+ *
  * @hide
  */
-// @SystemApi(client = MODULE_LIBRARIES)
+@SystemApi(client = MODULE_LIBRARIES)
 public class NetworkStatsDataMigrationUtils {
+    /**
+     * Prefix of the files which are used to store per network interface statistics.
+     */
+    public static final String PREFIX_XT = "xt";
+    /**
+     * Prefix of the files which are used to store per uid statistics.
+     */
+    public static final String PREFIX_UID = "uid";
+    /**
+     * Prefix of the files which are used to store per uid tagged traffic statistics.
+     */
+    public static final String PREFIX_UID_TAG = "uid_tag";
 
     private static final HashMap<String, String> sPrefixLegacyFileNameMap =
             new HashMap<String, String>() {{
@@ -146,17 +160,51 @@
     }
 
     /**
-     * Read legacy persisted network stats from disk. This function provides a default
-     * implementation to read persisted network stats from two different locations.
-     * And this is intended to be modified by OEM to read from custom file format or
-     * locations if necessary.
+     * Read legacy persisted network stats from disk.
+     *
+     * This function provides the implementation to read legacy network stats
+     * from disk. It is used for migration of legacy network stats into the
+     * stats provided by the Connectivity module.
+     * This function needs to know about the previous format(s) of the network
+     * stats data that might be stored on this device so it can be read and
+     * conserved upon upgrade to Android 13 or above.
+     *
+     * This function will be called multiple times sequentially, all on the
+     * same thread, and will not be called multiple times concurrently. This
+     * function is expected to do a substantial amount of disk access, and
+     * doesn't need to return particularly fast, but the first boot after
+     * an upgrade to Android 13+ will be held until migration is done. As
+     * migration is only necessary once, after the first boot following the
+     * upgrade, this delay is not incurred.
+     *
+     * If this function fails in any way, it should throw an exception. If this
+     * happens, the system can't know about the data that was stored in the
+     * legacy files, but it will still count data usage happening on this
+     * session. On the next boot, the system will try migration again, and
+     * merge the returned data with the data used with the previous session.
+     * The system will only try the migration up to three (3) times. The remaining
+     * count is stored in the netstats_import_legacy_file_needed device config. The
+     * legacy data is never deleted by the mainline module to avoid any possible
+     * data loss.
+     *
+     * It is possible to set the netstats_import_legacy_file_needed device config
+     * to any positive integer to force the module to perform the migration. This
+     * can be achieved by calling the following command before rebooting :
+     *     adb shell device_config put connectivity netstats_import_legacy_file_needed 1
+     *
+     * The AOSP implementation provides code to read persisted network stats as
+     * they were written by AOSP prior to Android 13.
+     * OEMs who have used the AOSP implementation of persisting network stats
+     * to disk don't need to change anything.
+     * OEM that had modifications to this format should modify this function
+     * to read from their custom file format or locations if necessary.
      *
      * @param prefix         Type of data which is being read by the service.
      * @param bucketDuration Duration of the buckets of the object, in milliseconds.
      * @return {@link NetworkStatsCollection} instance.
      */
     @NonNull
-    public static NetworkStatsCollection readPlatformCollectionLocked(
+    public static NetworkStatsCollection readPlatformCollection(
             @NonNull String prefix, long bucketDuration) throws IOException {
         final NetworkStatsCollection.Builder builder =
                 new NetworkStatsCollection.Builder(bucketDuration);
@@ -397,7 +445,7 @@
             if (version >= IdentitySetVersion.VERSION_ADD_OEM_MANAGED_NETWORK) {
                 oemNetCapabilities = in.readInt();
             } else {
-                oemNetCapabilities = NetworkIdentity.OEM_NONE;
+                oemNetCapabilities = NetworkTemplate.OEM_MANAGED_NO;
             }
 
             // Legacy files might contain TYPE_MOBILE_* types which were deprecated in later
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 2d33817..07a5132 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -34,6 +34,7 @@
 import android.service.batterystats.BatteryStatsServiceDumpHistoryProto;
 import android.service.batterystats.BatteryStatsServiceDumpProto;
 import android.telephony.CellSignalStrength;
+import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
 import android.text.format.DateFormat;
 import android.util.ArrayMap;
@@ -2654,6 +2655,46 @@
      */
     public abstract Timer getPhoneDataConnectionTimer(int dataType);
 
+    /** @hide */
+    public static final int RADIO_ACCESS_TECHNOLOGY_OTHER = 0;
+    /** @hide */
+    public static final int RADIO_ACCESS_TECHNOLOGY_LTE = 1;
+    /** @hide */
+    public static final int RADIO_ACCESS_TECHNOLOGY_NR = 2;
+    /** @hide */
+    public static final int RADIO_ACCESS_TECHNOLOGY_COUNT = 3;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "RADIO_ACCESS_TECHNOLOGY_",
+            value = {RADIO_ACCESS_TECHNOLOGY_OTHER, RADIO_ACCESS_TECHNOLOGY_LTE,
+                    RADIO_ACCESS_TECHNOLOGY_NR})
+    public @interface RadioAccessTechnology {
+    }
+
+    /** @hide */
+    public static final String[] RADIO_ACCESS_TECHNOLOGY_NAMES = {"Other", "LTE", "NR"};
+
+    /**
+     * Returns the time in microseconds that the mobile radio has been active on a
+     * given Radio Access Technology (RAT), at a given frequency (NR RAT only), for a given
+     * transmission power level.
+     *
+     * @param rat            Radio Access Technology {@see RadioAccessTechnology}
+     * @param frequencyRange frequency range {@see ServiceState.FrequencyRange}, only needed for
+     *                       RADIO_ACCESS_TECHNOLOGY_NR. Use
+     *                       {@link ServiceState.FREQUENCY_RANGE_UNKNOWN} for other Radio Access
+     *                       Technologies.
+     * @param signalStrength the cellular signal strength. {@see CellSignalStrength#getLevel()}
+     * @param elapsedRealtimeMs current elapsed realtime
+     * @return time (in milliseconds) the mobile radio spent active in the specified state,
+     *         while on battery.
+     * @hide
+     */
+    public abstract long getActiveRadioDurationMs(@RadioAccessTechnology int rat,
+            @ServiceState.FrequencyRange int frequencyRange, int signalStrength,
+            long elapsedRealtimeMs);
+
     static final String[] WIFI_SUPPL_STATE_NAMES = {
         "invalid", "disconn", "disabled", "inactive", "scanning",
         "authenticating", "associating", "associated", "4-way-handshake",
@@ -3997,6 +4038,89 @@
         }
     }
 
+    private void printCellularPerRatBreakdown(PrintWriter pw, StringBuilder sb, String prefix,
+            long rawRealtimeMs) {
+        final String allFrequenciesHeader =
+                "    All frequencies:\n";
+        final String[] nrFrequencyRangeDescription = new String[]{
+                "    Unknown frequency:\n",
+                "    Low frequency (less than 1GHz):\n",
+                "    Middle frequency (1GHz to 3GHz):\n",
+                "    High frequency (3GHz to 6GHz):\n",
+                "    Mmwave frequency (greater than 6GHz):\n"};
+        final String signalStrengthHeader =
+                "      Signal Strength Time:\n";
+        final String[] signalStrengthDescription = new String[]{
+                "        unknown:  ",
+                "        poor:     ",
+                "        moderate: ",
+                "        good:     ",
+                "        great:    "};
+
+        final long totalActiveTimesMs = getMobileRadioActiveTime(rawRealtimeMs * 1000,
+                STATS_SINCE_CHARGED) / 1000;
+
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("Active Cellular Radio Access Technology Breakdown:");
+        pw.println(sb);
+
+        boolean hasData = false;
+        final int numSignalStrength = CellSignalStrength.getNumSignalStrengthLevels();
+        for (int rat = RADIO_ACCESS_TECHNOLOGY_COUNT - 1; rat >= 0; rat--) {
+            sb.setLength(0);
+            sb.append(prefix);
+            sb.append("  ");
+            sb.append(RADIO_ACCESS_TECHNOLOGY_NAMES[rat]);
+            sb.append(":\n");
+            sb.append(prefix);
+
+            final int numFreqLvl =
+                    rat == RADIO_ACCESS_TECHNOLOGY_NR ? nrFrequencyRangeDescription.length : 1;
+            for (int freqLvl = numFreqLvl - 1; freqLvl >= 0; freqLvl--) {
+                final int freqDescriptionStart = sb.length();
+                boolean hasFreqData = false;
+                if (rat == RADIO_ACCESS_TECHNOLOGY_NR) {
+                    sb.append(nrFrequencyRangeDescription[freqLvl]);
+                } else {
+                    sb.append(allFrequenciesHeader);
+                }
+
+                sb.append(prefix);
+                sb.append(signalStrengthHeader);
+                for (int strength = 0; strength < numSignalStrength; strength++) {
+                    final long timeMs = getActiveRadioDurationMs(rat, freqLvl, strength,
+                            rawRealtimeMs);
+                    if (timeMs <= 0) continue;
+                    hasFreqData = true;
+                    sb.append(prefix);
+                    sb.append(signalStrengthDescription[strength]);
+                    formatTimeMs(sb, timeMs);
+                    sb.append("(");
+                    sb.append(formatRatioLocked(timeMs, totalActiveTimesMs));
+                    sb.append(")\n");
+                }
+
+                if (hasFreqData) {
+                    hasData = true;
+                    pw.print(sb);
+                    sb.setLength(0);
+                    sb.append(prefix);
+                } else {
+                    // No useful data was printed, rewind sb to before the start of this frequency.
+                    sb.setLength(freqDescriptionStart);
+                }
+            }
+        }
+
+        if (!hasData) {
+            sb.setLength(0);
+            sb.append(prefix);
+            sb.append("  (no activity)");
+            pw.println(sb);
+        }
+    }
+
     /**
      * Temporary for settings.
      */
@@ -5269,6 +5393,8 @@
         printControllerActivity(pw, sb, prefix, CELLULAR_CONTROLLER_NAME,
                 getModemControllerActivity(), which);
 
+        printCellularPerRatBreakdown(pw, sb, prefix + "     ", rawRealtimeMs);
+
         pw.print("     Cellular data received: "); pw.println(formatBytesLocked(mobileRxTotalBytes));
         pw.print("     Cellular data sent: "); pw.println(formatBytesLocked(mobileTxTotalBytes));
         pw.print("     Cellular packets received: "); pw.println(mobileRxTotalPackets);
diff --git a/core/java/android/os/BatteryStatsManager.java b/core/java/android/os/BatteryStatsManager.java
index 2a609b8..f16bbc6 100644
--- a/core/java/android/os/BatteryStatsManager.java
+++ b/core/java/android/os/BatteryStatsManager.java
@@ -24,6 +24,8 @@
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
 import android.content.Context;
 import android.net.NetworkStack;
 import android.os.connectivity.CellularBatteryStats;
@@ -515,6 +517,42 @@
     }
 
     /**
+     * Indicates that Bluetooth was toggled on.
+     *
+     * @param uid calling package uid
+     * @param reason why Bluetooth has been turned on
+     * @param packageName package responsible for this change
+     */
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    public void reportBluetoothOn(int uid, int reason, @NonNull String packageName) {
+        try {
+            mBatteryStats.noteBluetoothOn(uid, reason, packageName);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Indicates that Bluetooth was toggled off.
+     *
+     * @param uid calling package uid
+     * @param reason why Bluetooth has been turned on
+     * @param packageName package responsible for this change
+     */
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    public void reportBluetoothOff(int uid, int reason, @NonNull String packageName) {
+        try {
+            mBatteryStats.noteBluetoothOff(uid, reason, packageName);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Indicates that a new Bluetooth LE scan has started.
      *
      * @param ws worksource (to be used for battery blaming).
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index 425e797..df5b7bc 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -65,6 +65,11 @@
     boolean isBatteryDischargePredictionPersonalized();
     boolean isDeviceIdleMode();
     boolean isLightDeviceIdleMode();
+    boolean isLowPowerStandbySupported();
+    boolean isLowPowerStandbyEnabled();
+    void setLowPowerStandbyEnabled(boolean enabled);
+    void setLowPowerStandbyActiveDuringMaintenance(boolean activeDuringMaintenance);
+    void forceLowPowerStandbyActive(boolean active);
 
     @UnsupportedAppUsage
     void reboot(boolean confirm, String reason, boolean wait);
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 881fced..5bd8588 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -216,6 +216,17 @@
     public static final int UNIMPORTANT_FOR_LOGGING = 0x40000000;
 
     /**
+     * Wake lock flag: This wake lock should be held by the system.
+     *
+     * <p>Meant to allow tests to keep the device awake even when power restrictions are active.
+     *
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.DEVICE_POWER)
+    public static final int SYSTEM_WAKELOCK = 0x80000000;
+
+    /**
      * Flag for {@link WakeLock#release WakeLock.release(int)}: Defer releasing a
      * {@link #PROXIMITY_SCREEN_OFF_WAKE_LOCK} wake lock until the proximity sensor
      * indicates that an object is not in close proximity.
@@ -2146,6 +2157,105 @@
     }
 
     /**
+     * Returns true if Low Power Standby is supported on this device.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+            android.Manifest.permission.DEVICE_POWER
+    })
+    public boolean isLowPowerStandbySupported() {
+        try {
+            return mService.isLowPowerStandbySupported();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns true if Low Power Standby is enabled.
+     *
+     * <p>When Low Power Standby is enabled, apps (including apps running foreground services) are
+     * subject to additional restrictions while the device is non-interactive, outside of device
+     * idle maintenance windows: Their network access is disabled, and any wakelocks they hold are
+     * ignored.
+     *
+     * <p>When Low Power Standby is enabled or disabled, a Intent with action
+     * {@link #ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED} is broadcast to registered receivers.
+     */
+    public boolean isLowPowerStandbyEnabled() {
+        try {
+            return mService.isLowPowerStandbyEnabled();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Set whether Low Power Standby is enabled.
+     * Does nothing if Low Power Standby is not supported.
+     *
+     * @see #isLowPowerStandbySupported()
+     * @see #isLowPowerStandbyEnabled()
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+            android.Manifest.permission.DEVICE_POWER
+    })
+    public void setLowPowerStandbyEnabled(boolean enabled) {
+        try {
+            mService.setLowPowerStandbyEnabled(enabled);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Set whether Low Power Standby should be active during doze maintenance mode.
+     * Does nothing if Low Power Standby is not supported.
+     *
+     * @see #isLowPowerStandbySupported()
+     * @see #isLowPowerStandbyEnabled()
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+            android.Manifest.permission.DEVICE_POWER
+    })
+    public void setLowPowerStandbyActiveDuringMaintenance(boolean activeDuringMaintenance) {
+        try {
+            mService.setLowPowerStandbyActiveDuringMaintenance(activeDuringMaintenance);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Force Low Power Standby restrictions to be active.
+     * Does nothing if Low Power Standby is not supported.
+     *
+     * @see #isLowPowerStandbySupported()
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+            android.Manifest.permission.DEVICE_POWER
+    })
+    public void forceLowPowerStandbyActive(boolean active) {
+        try {
+            mService.forceLowPowerStandbyActive(active);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Return whether the given application package name is on the device's power allowlist.
      * Apps can be placed on the allowlist through the settings UI invoked by
      * {@link android.provider.Settings#ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS}.
@@ -2631,6 +2741,16 @@
             = "android.os.action.POWER_SAVE_TEMP_WHITELIST_CHANGED";
 
     /**
+     * Intent that is broadcast when Low Power Standby is enabled or disabled.
+     * This broadcast is only sent to registered receivers.
+     *
+     * @see #isLowPowerStandbyEnabled()
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED =
+            "android.os.action.LOW_POWER_STANDBY_ENABLED_CHANGED";
+
+    /**
      * Constant for PreIdleTimeout normal mode (default mode, not short nor extend timeout) .
      * @hide
      */
diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java
index eb18b96..ec4d3b6 100644
--- a/core/java/android/os/PowerManagerInternal.java
+++ b/core/java/android/os/PowerManagerInternal.java
@@ -184,6 +184,21 @@
 
     public abstract void setDeviceIdleTempWhitelist(int[] appids);
 
+    /**
+     * Updates the Low Power Standby allowlist.
+     *
+     * @param uids UIDs that are exempt from Low Power Standby restrictions
+     */
+    public abstract void setLowPowerStandbyAllowlist(int[] uids);
+
+    /**
+     * Used by LowPowerStandbyController to notify the power manager that Low Power Standby's
+     * active state has changed.
+     *
+     * @param active {@code true} to activate Low Power Standby, {@code false} to turn it off.
+     */
+    public abstract void setLowPowerStandbyActive(boolean active);
+
     public abstract void startUidChanges();
 
     public abstract void finishUidChanges();
diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java
index 8bc219b..49c0520 100644
--- a/core/java/android/os/VibrationAttributes.java
+++ b/core/java/android/os/VibrationAttributes.java
@@ -37,6 +37,7 @@
             USAGE_CLASS_UNKNOWN,
             USAGE_CLASS_ALARM,
             USAGE_CLASS_FEEDBACK,
+            USAGE_CLASS_MEDIA,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface UsageClass {}
@@ -459,4 +460,3 @@
         }
     }
 }
-
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 8df659d..63616da 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -291,6 +291,8 @@
     public static final int FLAG_INCLUDE_INVISIBLE = 1 << 10;
     /** {@hide} */
     public static final int FLAG_INCLUDE_RECENT = 1 << 11;
+    /** {@hide} */
+    public static final int FLAG_INCLUDE_SHARED_PROFILE = 1 << 12;
 
     /** {@hide} */
     public static final int FSTRIM_FLAG_DEEP = IVold.FSTRIM_FLAG_DEEP_TRIM;
@@ -1328,6 +1330,23 @@
     }
 
     /**
+     * Return the list of shared/external storage volumes currently available to
+     * the calling user and the user it shares media with
+     * CDD link : https://source.android.com/compatibility/12/android-12-cdd#95_multi-user_support
+     * <p>
+     * This is similar to {@link StorageManager#getStorageVolumes()} except that the result also
+     * includes the volumes belonging to any user it shares media with
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE)
+    public @NonNull List<StorageVolume> getStorageVolumesIncludingSharedProfiles() {
+        final ArrayList<StorageVolume> res = new ArrayList<>();
+        Collections.addAll(res,
+                getVolumeList(mContext.getUserId(),
+                        FLAG_REAL_STATE | FLAG_INCLUDE_INVISIBLE | FLAG_INCLUDE_SHARED_PROFILE));
+        return res;
+    }
+
+    /**
      * Return the list of shared/external storage volumes both currently and
      * recently available to the calling user.
      * <p>
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index 8ee52c2..e1f112a 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -16,8 +16,6 @@
 
 package android.os.storage;
 
-import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
@@ -308,11 +306,9 @@
 
     /**
      * Returns the user that owns this volume
-     *
-     * {@hide}
      */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
-    @SystemApi(client = MODULE_LIBRARIES)
+    // TODO(b/193460475) : Android Lint handle API change from systemApi to public Api incorrectly
+    @SuppressLint("NewApi")
     public @NonNull UserHandle getOwner() {
         return mOwner;
     }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 2a563ac..14055ac 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2386,6 +2386,15 @@
             "android.settings.ENABLE_MMS_DATA_REQUEST";
 
     /**
+     * Shows restrict settings dialog when settings is blocked.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_SHOW_RESTRICTED_SETTING_DIALOG =
+            "android.settings.SHOW_RESTRICTED_SETTING_DIALOG";
+
+    /**
      * Integer value that specifies the reason triggering enable MMS data notification.
      * This must be passed as an extra field to the {@link #ACTION_ENABLE_MMS_DATA_REQUEST}.
      * Extra with value of EnableMmsDataReason interface.
@@ -6574,6 +6583,8 @@
          * @hide
          */
         @Readable(maxTargetSdk = Build.VERSION_CODES.S)
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+        @SuppressLint("NoSettingsProvider")
         public static final String BLUETOOTH_NAME = "bluetooth_name";
 
         /**
@@ -6581,6 +6592,8 @@
          * @hide
          */
         @Readable(maxTargetSdk = Build.VERSION_CODES.S)
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+        @SuppressLint("NoSettingsProvider")
         public static final String BLUETOOTH_ADDRESS = "bluetooth_address";
 
         /**
@@ -6588,6 +6601,8 @@
          * @hide
          */
         @Readable(maxTargetSdk = Build.VERSION_CODES.S)
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+        @SuppressLint("NoSettingsProvider")
         public static final String BLUETOOTH_ADDR_VALID = "bluetooth_addr_valid";
 
         /**
@@ -10259,6 +10274,13 @@
         public static final String NEARBY_SHARING_COMPONENT = "nearby_sharing_component";
 
         /**
+         * Nearby Sharing Slice URI for the SliceProvider to
+         * read Nearby Sharing scan results and then draw the UI.
+         * @hide
+         */
+        public static final String NEARBY_SHARING_SLICE_URI = "nearby_sharing_slice_uri";
+
+        /**
          * Controls whether aware is enabled.
          * @hide
          */
@@ -10826,6 +10848,8 @@
          * @hide
          */
         @Readable
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+        @SuppressLint("NoSettingsProvider")
         public static final String BLUETOOTH_CLASS_OF_DEVICE = "bluetooth_class_of_device";
 
         /**
@@ -10834,6 +10858,8 @@
          * {@hide}
          */
         @Readable
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+        @SuppressLint("NoSettingsProvider")
         public static final String BLUETOOTH_DISABLED_PROFILES = "bluetooth_disabled_profiles";
 
         /**
@@ -12371,6 +12397,8 @@
         * @hide
         */
         @Readable
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+        @SuppressLint("NoSettingsProvider")
         public static final String BLE_SCAN_ALWAYS_AVAILABLE = "ble_scan_always_enabled";
 
         /**
@@ -12378,6 +12406,8 @@
          * @hide
          */
         @Readable
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+        @SuppressLint("NoSettingsProvider")
         public static final String BLE_SCAN_LOW_POWER_WINDOW_MS = "ble_scan_low_power_window_ms";
 
         /**
@@ -12385,6 +12415,8 @@
          * @hide
          */
         @Readable
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+        @SuppressLint("NoSettingsProvider")
         public static final String BLE_SCAN_BALANCED_WINDOW_MS = "ble_scan_balanced_window_ms";
 
         /**
@@ -12392,6 +12424,8 @@
          * @hide
          */
         @Readable
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+        @SuppressLint("NoSettingsProvider")
         public static final String BLE_SCAN_LOW_LATENCY_WINDOW_MS =
                 "ble_scan_low_latency_window_ms";
 
@@ -12400,6 +12434,8 @@
          * @hide
          */
         @Readable
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+        @SuppressLint("NoSettingsProvider")
         public static final String BLE_SCAN_LOW_POWER_INTERVAL_MS =
                 "ble_scan_low_power_interval_ms";
 
@@ -12408,6 +12444,8 @@
          * @hide
          */
         @Readable
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+        @SuppressLint("NoSettingsProvider")
         public static final String BLE_SCAN_BALANCED_INTERVAL_MS =
                 "ble_scan_balanced_interval_ms";
 
@@ -12416,6 +12454,8 @@
          * @hide
          */
         @Readable
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+        @SuppressLint("NoSettingsProvider")
         public static final String BLE_SCAN_LOW_LATENCY_INTERVAL_MS =
                 "ble_scan_low_latency_interval_ms";
 
@@ -12424,6 +12464,8 @@
          * @hide
          */
         @Readable
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+        @SuppressLint("NoSettingsProvider")
         public static final String BLE_SCAN_BACKGROUND_MODE = "ble_scan_background_mode";
 
         /**
@@ -13156,6 +13198,8 @@
 
         /** {@hide} */
         @Readable
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+        @SuppressLint("NoSettingsProvider")
         public static final String
                 BLUETOOTH_BTSNOOP_DEFAULT_MODE = "bluetooth_btsnoop_default_mode";
         /** {@hide} */
@@ -16706,6 +16750,30 @@
         public static final String RESTRICTED_NETWORKING_MODE = "restricted_networking_mode";
 
         /**
+         * Setting indicating whether Low Power Standby is enabled, if supported.
+         *
+         * Values are:
+         * 0: disabled
+         * 1: enabled
+         *
+         * @hide
+         */
+        public static final String LOW_POWER_STANDBY_ENABLED = "low_power_standby_enabled";
+
+        /**
+         * Setting indicating whether Low Power Standby is allowed to be active during doze
+         * maintenance mode.
+         *
+         * Values are:
+         * 0: Low Power Standby will be disabled during doze maintenance mode
+         * 1: Low Power Standby can be active during doze maintenance mode
+         *
+         * @hide
+         */
+        public static final String LOW_POWER_STANDBY_ACTIVE_DURING_MAINTENANCE =
+                "low_power_standby_active_during_maintenance";
+
+        /**
          * Settings migrated from Wear OS settings provider.
          * @hide
          */
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index 29c7796..cb1b5d3 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -578,6 +578,14 @@
     public static final String SERVICE_META_DATA = "android.autofill";
 
     /**
+     * Name of the {@link FillResponse} extra used to return a delayed fill response.
+     *
+     * <p>Please see {@link FillRequest#getDelayedFillIntentSender()} on how to send a delayed
+     * fill response to framework.</p>
+     */
+    public static final String EXTRA_FILL_RESPONSE = "android.service.autofill.extra.FILL_RESPONSE";
+
+    /**
      * Name of the {@link IResultReceiver} extra used to return the primary result of a request.
      *
      * @hide
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index 86341a9..cfb6909 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -65,6 +65,16 @@
  * can be shown by the keyboard as a suggestion. To use this feature, the Dataset should contain
  * an {@link InlinePresentation} representing how the inline suggestion UI will be rendered.
  *
+ * <a name="FillDialogUI"></a>
+ * <h3>Fill Dialog UI</h3>
+ *
+ * <p>The fill dialog UI is a more conspicuous and efficient interface than dropdown UI. If autofill
+ * suggestions are available when the user clicks on a field that supports filling the dialog UI,
+ * Autofill will pop up a fill dialog. The dialog will take up a larger area to display the
+ * datasets, so it is easy for users to pay attention to the datasets and selecting a dataset.
+ * If the user focuses on the view before suggestions are available, will fall back to dropdown UI
+ * or inline suggestions.
+ *
  * <a name="Authentication"></a>
  * <h3>Dataset authentication</h3>
  *
@@ -92,10 +102,9 @@
  * <ol>
  *   <li>If the view's {@link android.view.View#getAutofillValue() autofill value} is not
  * {@link AutofillValue#isText() text} or is empty, all datasets are shown.
- *   <li>Datasets that have a filter regex (set through
- * {@link Dataset.Builder#setValue(AutofillId, AutofillValue, Pattern)} or
- * {@link Dataset.Builder#setValue(AutofillId, AutofillValue, Pattern, RemoteViews)}) and whose
- * regex matches the view's text value converted to lower case are shown.
+ *   <li>Datasets that have a filter regex (set through {@link Field.Builder#setFilter(Pattern)}
+ *   and {@link Dataset.Builder#setField(AutofillId, Field)}) and whose regex matches the view's
+ *   text value converted to lower case are shown.
  *   <li>Datasets that do not require authentication, have a field value that is
  * {@link AutofillValue#isText() text} and whose {@link AutofillValue#getTextValue() value} starts
  * with the lower case value of the view's text are shown.
@@ -107,11 +116,13 @@
     private final ArrayList<AutofillId> mFieldIds;
     private final ArrayList<AutofillValue> mFieldValues;
     private final ArrayList<RemoteViews> mFieldPresentations;
+    private final ArrayList<RemoteViews> mFieldDialogPresentations;
     private final ArrayList<InlinePresentation> mFieldInlinePresentations;
     private final ArrayList<InlinePresentation> mFieldInlineTooltipPresentations;
     private final ArrayList<DatasetFieldFilter> mFieldFilters;
     @Nullable private final ClipData mFieldContent;
     private final RemoteViews mPresentation;
+    private final RemoteViews mDialogPresentation;
     @Nullable private final InlinePresentation mInlinePresentation;
     @Nullable private final InlinePresentation mInlineTooltipPresentation;
     private final IntentSender mAuthentication;
@@ -121,11 +132,13 @@
         mFieldIds = builder.mFieldIds;
         mFieldValues = builder.mFieldValues;
         mFieldPresentations = builder.mFieldPresentations;
+        mFieldDialogPresentations = builder.mFieldDialogPresentations;
         mFieldInlinePresentations = builder.mFieldInlinePresentations;
         mFieldInlineTooltipPresentations = builder.mFieldInlineTooltipPresentations;
         mFieldFilters = builder.mFieldFilters;
         mFieldContent = builder.mFieldContent;
         mPresentation = builder.mPresentation;
+        mDialogPresentation = builder.mDialogPresentation;
         mInlinePresentation = builder.mInlinePresentation;
         mInlineTooltipPresentation = builder.mInlineTooltipPresentation;
         mAuthentication = builder.mAuthentication;
@@ -153,6 +166,12 @@
     }
 
     /** @hide */
+    public RemoteViews getFieldDialogPresentation(int index) {
+        final RemoteViews customPresentation = mFieldDialogPresentations.get(index);
+        return customPresentation != null ? customPresentation : mDialogPresentation;
+    }
+
+    /** @hide */
     public @Nullable InlinePresentation getFieldInlinePresentation(int index) {
         final InlinePresentation inlinePresentation = mFieldInlinePresentations.get(index);
         return inlinePresentation != null ? inlinePresentation : mInlinePresentation;
@@ -219,6 +238,9 @@
         if (mFieldPresentations != null) {
             builder.append(", fieldPresentations=").append(mFieldPresentations.size());
         }
+        if (mFieldDialogPresentations != null) {
+            builder.append(", fieldDialogPresentations=").append(mFieldDialogPresentations.size());
+        }
         if (mFieldInlinePresentations != null) {
             builder.append(", fieldInlinePresentations=").append(mFieldInlinePresentations.size());
         }
@@ -232,6 +254,9 @@
         if (mPresentation != null) {
             builder.append(", hasPresentation");
         }
+        if (mDialogPresentation != null) {
+            builder.append(", hasDialogPresentation");
+        }
         if (mInlinePresentation != null) {
             builder.append(", hasInlinePresentation");
         }
@@ -264,11 +289,13 @@
         private ArrayList<AutofillId> mFieldIds;
         private ArrayList<AutofillValue> mFieldValues;
         private ArrayList<RemoteViews> mFieldPresentations;
+        private ArrayList<RemoteViews> mFieldDialogPresentations;
         private ArrayList<InlinePresentation> mFieldInlinePresentations;
         private ArrayList<InlinePresentation> mFieldInlineTooltipPresentations;
         private ArrayList<DatasetFieldFilter> mFieldFilters;
         @Nullable private ClipData mFieldContent;
         private RemoteViews mPresentation;
+        private RemoteViews mDialogPresentation;
         @Nullable private InlinePresentation mInlinePresentation;
         @Nullable private InlinePresentation mInlineTooltipPresentation;
         private IntentSender mAuthentication;
@@ -279,7 +306,9 @@
          * Creates a new builder.
          *
          * @param presentation The presentation used to visualize this dataset.
+         * @deprecated Use {@link #Builder(Presentations)} instead.
          */
+        @Deprecated
         public Builder(@NonNull RemoteViews presentation) {
             Objects.requireNonNull(presentation, "presentation must be non-null");
             mPresentation = presentation;
@@ -294,19 +323,34 @@
          *              as inline suggestions. If the dataset supports inline suggestions,
          *              this should not be null.
          * @hide
+         * @deprecated Use {@link #Builder(Presentations)} instead.
          */
         @SystemApi
+        @Deprecated
         public Builder(@NonNull InlinePresentation inlinePresentation) {
             Objects.requireNonNull(inlinePresentation, "inlinePresentation must be non-null");
             mInlinePresentation = inlinePresentation;
         }
 
         /**
+         * Creates a new builder.
+         *
+         * @param presentations The presentations used to visualize this dataset.
+         */
+        public Builder(@NonNull Presentations presentations) {
+            Objects.requireNonNull(presentations, "presentations must be non-null");
+
+            mPresentation = presentations.getMenuPresentation();
+            mInlinePresentation = presentations.getInlinePresentation();
+            mInlineTooltipPresentation = presentations.getInlineTooltipPresentation();
+            mDialogPresentation = presentations.getDialogPresentation();
+        }
+
+        /**
          * Creates a new builder for a dataset where each field will be visualized independently.
          *
-         * <p>When using this constructor, fields must be set through
-         * {@link #setValue(AutofillId, AutofillValue, RemoteViews)} or
-         * {@link #setValue(AutofillId, AutofillValue, Pattern, RemoteViews)}.
+         * <p>When using this constructor, a presentation must be provided for each field through
+         * {@link #setField(AutofillId, Field)}.
          */
         public Builder() {
         }
@@ -318,7 +362,9 @@
          * @throws IllegalStateException if {@link #build()} was already called.
          *
          * @return this builder.
+         * @deprecated Use {@link #Builder(Presentations)} instead.
          */
+        @Deprecated
         public @NonNull Builder setInlinePresentation(
                 @NonNull InlinePresentation inlinePresentation) {
             throwIfDestroyed();
@@ -339,7 +385,9 @@
          * @throws IllegalStateException if {@link #build()} was already called.
          *
          * @return this builder.
+         * @deprecated Use {@link #Builder(Presentations)} instead.
          */
+        @Deprecated
         public @NonNull Builder setInlinePresentation(
                 @NonNull InlinePresentation inlinePresentation,
                 @NonNull InlinePresentation inlineTooltipPresentation) {
@@ -479,7 +527,7 @@
                             "Content items cannot contain an Intent: content=" + content);
                 }
             }
-            setLifeTheUniverseAndEverything(id, null, null, null, null);
+            setLifeTheUniverseAndEverything(id, null, null, null, null, null, null);
             mFieldContent = content;
             return this;
         }
@@ -509,10 +557,12 @@
          * @throws IllegalStateException if {@link #build()} was already called.
          *
          * @return this builder.
+         * @deprecated Use {@link #setField(AutofillId, Field)} instead.
          */
+        @Deprecated
         public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value) {
             throwIfDestroyed();
-            setLifeTheUniverseAndEverything(id, value, null, null, null);
+            setLifeTheUniverseAndEverything(id, value, null, null, null, null, null);
             return this;
         }
 
@@ -537,12 +587,14 @@
          * @throws IllegalStateException if {@link #build()} was already called.
          *
          * @return this builder.
+         * @deprecated Use {@link #setField(AutofillId, Field)} instead.
          */
+        @Deprecated
         public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
                 @NonNull RemoteViews presentation) {
             throwIfDestroyed();
             Objects.requireNonNull(presentation, "presentation cannot be null");
-            setLifeTheUniverseAndEverything(id, value, presentation, null, null);
+            setLifeTheUniverseAndEverything(id, value, presentation, null, null, null, null);
             return this;
         }
 
@@ -572,13 +624,16 @@
          * @return this builder.
          * @throws IllegalStateException if the builder was constructed without a
          *         {@link RemoteViews presentation} or {@link #build()} was already called.
+         * @deprecated Use {@link #setField(AutofillId, Field)} instead.
          */
+        @Deprecated
         public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
                 @Nullable Pattern filter) {
             throwIfDestroyed();
             Preconditions.checkState(mPresentation != null,
                     "Dataset presentation not set on constructor");
-            setLifeTheUniverseAndEverything(id, value, null, null, new DatasetFieldFilter(filter));
+            setLifeTheUniverseAndEverything(
+                    id, value, null, null, null, new DatasetFieldFilter(filter), null);
             return this;
         }
 
@@ -610,13 +665,15 @@
          * @throws IllegalStateException if {@link #build()} was already called.
          *
          * @return this builder.
+         * @deprecated Use {@link #setField(AutofillId, Field)} instead.
          */
+        @Deprecated
         public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
                 @Nullable Pattern filter, @NonNull RemoteViews presentation) {
             throwIfDestroyed();
             Objects.requireNonNull(presentation, "presentation cannot be null");
-            setLifeTheUniverseAndEverything(id, value, presentation, null,
-                    new DatasetFieldFilter(filter));
+            setLifeTheUniverseAndEverything(id, value, presentation, null, null,
+                    new DatasetFieldFilter(filter), null);
             return this;
         }
 
@@ -641,13 +698,16 @@
          * @throws IllegalStateException if {@link #build()} was already called.
          *
          * @return this builder.
+         * @deprecated Use {@link #setField(AutofillId, Field)} instead.
          */
+        @Deprecated
         public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
                 @NonNull RemoteViews presentation, @NonNull InlinePresentation inlinePresentation) {
             throwIfDestroyed();
             Objects.requireNonNull(presentation, "presentation cannot be null");
             Objects.requireNonNull(inlinePresentation, "inlinePresentation cannot be null");
-            setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation, null);
+            setLifeTheUniverseAndEverything(
+                    id, value, presentation, inlinePresentation, null, null, null);
             return this;
         }
 
@@ -672,7 +732,9 @@
          * @throws IllegalStateException if {@link #build()} was already called.
          *
          * @return this builder.
+         * @deprecated Use {@link #setField(AutofillId, Field)} instead.
          */
+        @Deprecated
         public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
                 @NonNull RemoteViews presentation, @NonNull InlinePresentation inlinePresentation,
                 @NonNull InlinePresentation inlineTooltipPresentation) {
@@ -682,7 +744,7 @@
             Objects.requireNonNull(inlineTooltipPresentation,
                     "inlineTooltipPresentation cannot be null");
             setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation,
-                    inlineTooltipPresentation, null);
+                    inlineTooltipPresentation, null, null);
             return this;
         }
 
@@ -718,15 +780,17 @@
          * @throws IllegalStateException if {@link #build()} was already called.
          *
          * @return this builder.
+         * @deprecated Use {@link #setField(AutofillId, Field)} instead.
          */
+        @Deprecated
         public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
                 @Nullable Pattern filter, @NonNull RemoteViews presentation,
                 @NonNull InlinePresentation inlinePresentation) {
             throwIfDestroyed();
             Objects.requireNonNull(presentation, "presentation cannot be null");
             Objects.requireNonNull(inlinePresentation, "inlinePresentation cannot be null");
-            setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation,
-                    new DatasetFieldFilter(filter));
+            setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation, null,
+                    new DatasetFieldFilter(filter), null);
             return this;
         }
 
@@ -756,7 +820,9 @@
          * @throws IllegalStateException if {@link #build()} was already called.
          *
          * @return this builder.
+         * @deprecated Use {@link #setField(AutofillId, Field)} instead.
          */
+        @Deprecated
         public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
                 @Nullable Pattern filter, @NonNull RemoteViews presentation,
                 @NonNull InlinePresentation inlinePresentation,
@@ -767,7 +833,91 @@
             Objects.requireNonNull(inlineTooltipPresentation,
                     "inlineTooltipPresentation cannot be null");
             setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation,
-                    inlineTooltipPresentation, new DatasetFieldFilter(filter));
+                    inlineTooltipPresentation, new DatasetFieldFilter(filter), null);
+            return this;
+        }
+
+        /**
+         * Sets the value of a field.
+         *
+         * Before Android 13, this information could be provided using several overloaded
+         * setValue(...) methods. This method replaces those with a Builder pattern.
+         * For example, in the old workflow, the app sets a field would be:
+         * <pre class="prettyprint">
+         *  Dataset.Builder dataset = new Dataset.Builder();
+         *  if (filter != null) {
+         *      if (presentation != null) {
+         *          if (inlinePresentation != null) {
+         *              dataset.setValue(id, value, filter, presentation, inlinePresentation)
+         *          } else {
+         *              dataset.setValue(id, value, filter, presentation);
+         *          }
+         *      } else {
+         *          dataset.setValue(id, value, filter);
+         *      }
+         *  } else {
+         *      if (presentation != null) {
+         *          if (inlinePresentation != null) {
+         *              dataset.setValue(id, value, presentation, inlinePresentation)
+         *          } else {
+         *              dataset.setValue(id, value, presentation);
+         *          }
+         *      } else {
+         *          dataset.setValue(id, value);
+         *      }
+         *  }
+         *  </pre>
+         * <p>The new workflow would be:
+         * <pre class="prettyprint">
+         * Field.Builder fieldBuilder = new Field.Builder();
+         * if (value != null) {
+         *     fieldBuilder.setValue(value);
+         * }
+         * if (filter != null) {
+         *     fieldBuilder.setFilter(filter);
+         * }
+         * Presentations.Builder presentationsBuilder = new Presentations.Builder(id);
+         * if (presentation != null) {
+         *     presentationsBuilder.setMenuPresentation(presentation);
+         * }
+         * if (inlinePresentation != null) {
+         *     presentationsBuilder.setInlinePresentation(inlinePresentation);
+         * }
+         * if (dialogPresentation != null) {
+         *     presentationsBuilder.setDialogPresentation(dialogPresentation);
+         * }
+         * fieldBuilder.setPresentations(presentationsBuilder.build());
+         * dataset.setField(id, fieldBuilder.build());
+         * </pre>
+         *
+         * @see Field
+         *
+         * @param id id returned by {@link
+         *         android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
+         * @param field the fill information about the field.
+         *
+         * @throws IllegalStateException if {@link #build()} was already called.
+         *
+         * @return this builder.
+         */
+        public @NonNull Builder setField(@NonNull AutofillId id, @Nullable Field field) {
+            throwIfDestroyed();
+            if (field == null) {
+                setLifeTheUniverseAndEverything(id, null, null, null, null, null, null);
+            } else {
+                final DatasetFieldFilter filter = field.getFilter();
+                final Presentations presentations = field.getPresentations();
+                if (presentations == null) {
+                    setLifeTheUniverseAndEverything(id, field.getValue(), null, null, null,
+                            filter, null);
+                } else {
+                    setLifeTheUniverseAndEverything(id, field.getValue(),
+                            presentations.getMenuPresentation(),
+                            presentations.getInlinePresentation(),
+                            presentations.getInlineTooltipPresentation(), filter,
+                            presentations.getDialogPresentation());
+                }
+            }
             return this;
         }
 
@@ -793,39 +943,34 @@
          * @throws IllegalStateException if {@link #build()} was already called.
          *
          * @return this builder.
-         *
+         * @deprecated Use {@link #setField(AutofillId, Field)} instead.
          * @hide
          */
+        @Deprecated
         @SystemApi
         public @NonNull Builder setFieldInlinePresentation(@NonNull AutofillId id,
                 @Nullable AutofillValue value, @Nullable Pattern filter,
                 @NonNull InlinePresentation inlinePresentation) {
             throwIfDestroyed();
             Objects.requireNonNull(inlinePresentation, "inlinePresentation cannot be null");
-            setLifeTheUniverseAndEverything(id, value, null, inlinePresentation,
-                    new DatasetFieldFilter(filter));
+            setLifeTheUniverseAndEverything(id, value, null, inlinePresentation, null,
+                    new DatasetFieldFilter(filter), null);
             return this;
         }
 
         private void setLifeTheUniverseAndEverything(@NonNull AutofillId id,
                 @Nullable AutofillValue value, @Nullable RemoteViews presentation,
                 @Nullable InlinePresentation inlinePresentation,
-                @Nullable DatasetFieldFilter filter) {
-            setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation, null,
-                    filter);
-        }
-
-        private void setLifeTheUniverseAndEverything(@NonNull AutofillId id,
-                @Nullable AutofillValue value, @Nullable RemoteViews presentation,
-                @Nullable InlinePresentation inlinePresentation,
                 @Nullable InlinePresentation tooltip,
-                @Nullable DatasetFieldFilter filter) {
+                @Nullable DatasetFieldFilter filter,
+                @Nullable RemoteViews dialogPresentation) {
             Objects.requireNonNull(id, "id cannot be null");
             if (mFieldIds != null) {
                 final int existingIdx = mFieldIds.indexOf(id);
                 if (existingIdx >= 0) {
                     mFieldValues.set(existingIdx, value);
                     mFieldPresentations.set(existingIdx, presentation);
+                    mFieldDialogPresentations.set(existingIdx, dialogPresentation);
                     mFieldInlinePresentations.set(existingIdx, inlinePresentation);
                     mFieldInlineTooltipPresentations.set(existingIdx, tooltip);
                     mFieldFilters.set(existingIdx, filter);
@@ -835,6 +980,7 @@
                 mFieldIds = new ArrayList<>();
                 mFieldValues = new ArrayList<>();
                 mFieldPresentations = new ArrayList<>();
+                mFieldDialogPresentations = new ArrayList<>();
                 mFieldInlinePresentations = new ArrayList<>();
                 mFieldInlineTooltipPresentations = new ArrayList<>();
                 mFieldFilters = new ArrayList<>();
@@ -842,6 +988,7 @@
             mFieldIds.add(id);
             mFieldValues.add(value);
             mFieldPresentations.add(presentation);
+            mFieldDialogPresentations.add(dialogPresentation);
             mFieldInlinePresentations.add(inlinePresentation);
             mFieldInlineTooltipPresentations.add(tooltip);
             mFieldFilters.add(filter);
@@ -853,10 +1000,7 @@
          * <p>You should not interact with this builder once this method is called.
          *
          * @throws IllegalStateException if no field was set (through
-         * {@link #setValue(AutofillId, AutofillValue)} or
-         * {@link #setValue(AutofillId, AutofillValue, RemoteViews)} or
-         * {@link #setValue(AutofillId, AutofillValue, RemoteViews, InlinePresentation)}),
-         * or if {@link #build()} was already called.
+         * {@link #setField(AutofillId, Field)}), or if {@link #build()} was already called.
          *
          * @return The built dataset.
          */
@@ -897,11 +1041,13 @@
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
         parcel.writeParcelable(mPresentation, flags);
+        parcel.writeParcelable(mDialogPresentation, flags);
         parcel.writeParcelable(mInlinePresentation, flags);
         parcel.writeParcelable(mInlineTooltipPresentation, flags);
         parcel.writeTypedList(mFieldIds, flags);
         parcel.writeTypedList(mFieldValues, flags);
         parcel.writeTypedList(mFieldPresentations, flags);
+        parcel.writeTypedList(mFieldDialogPresentations, flags);
         parcel.writeTypedList(mFieldInlinePresentations, flags);
         parcel.writeTypedList(mFieldInlineTooltipPresentations, flags);
         parcel.writeTypedList(mFieldFilters, flags);
@@ -913,8 +1059,12 @@
     public static final @NonNull Creator<Dataset> CREATOR = new Creator<Dataset>() {
         @Override
         public Dataset createFromParcel(Parcel parcel) {
-            final RemoteViews presentation = parcel.readParcelable(null, android.widget.RemoteViews.class);
-            final InlinePresentation inlinePresentation = parcel.readParcelable(null, android.service.autofill.InlinePresentation.class);
+            final RemoteViews presentation = parcel.readParcelable(null,
+                    android.widget.RemoteViews.class);
+            final RemoteViews dialogPresentation = parcel.readParcelable(null,
+                    android.widget.RemoteViews.class);
+            final InlinePresentation inlinePresentation = parcel.readParcelable(null,
+                    android.service.autofill.InlinePresentation.class);
             final InlinePresentation inlineTooltipPresentation =
                     parcel.readParcelable(null, android.service.autofill.InlinePresentation.class);
             final ArrayList<AutofillId> ids =
@@ -923,27 +1073,41 @@
                     parcel.createTypedArrayList(AutofillValue.CREATOR);
             final ArrayList<RemoteViews> presentations =
                     parcel.createTypedArrayList(RemoteViews.CREATOR);
+            final ArrayList<RemoteViews> dialogPresentations =
+                    parcel.createTypedArrayList(RemoteViews.CREATOR);
             final ArrayList<InlinePresentation> inlinePresentations =
                     parcel.createTypedArrayList(InlinePresentation.CREATOR);
             final ArrayList<InlinePresentation> inlineTooltipPresentations =
                     parcel.createTypedArrayList(InlinePresentation.CREATOR);
             final ArrayList<DatasetFieldFilter> filters =
                     parcel.createTypedArrayList(DatasetFieldFilter.CREATOR);
-            final ClipData fieldContent = parcel.readParcelable(null, android.content.ClipData.class);
-            final IntentSender authentication = parcel.readParcelable(null, android.content.IntentSender.class);
+            final ClipData fieldContent = parcel.readParcelable(null,
+                    android.content.ClipData.class);
+            final IntentSender authentication = parcel.readParcelable(null,
+                    android.content.IntentSender.class);
             final String datasetId = parcel.readString();
 
             // Always go through the builder to ensure the data ingested by
             // the system obeys the contract of the builder to avoid attacks
             // using specially crafted parcels.
-            final Builder builder = (presentation != null) ? new Builder(presentation)
-                    : new Builder();
-            if (inlinePresentation != null) {
-                if (inlineTooltipPresentation != null) {
-                    builder.setInlinePresentation(inlinePresentation, inlineTooltipPresentation);
-                } else {
-                    builder.setInlinePresentation(inlinePresentation);
+            final Builder builder;
+            if (presentation != null || inlinePresentation != null || dialogPresentation != null) {
+                final Presentations.Builder presentationsBuilder = new Presentations.Builder();
+                if (presentation != null) {
+                    presentationsBuilder.setMenuPresentation(presentation);
                 }
+                if (inlinePresentation != null) {
+                    presentationsBuilder.setInlinePresentation(inlinePresentation);
+                }
+                if (inlineTooltipPresentation != null) {
+                    presentationsBuilder.setInlineTooltipPresentation(inlineTooltipPresentation);
+                }
+                if (dialogPresentation != null) {
+                    presentationsBuilder.setDialogPresentation(dialogPresentation);
+                }
+                builder = new Builder(presentationsBuilder.build());
+            } else {
+                builder = new Builder();
             }
 
             if (fieldContent != null) {
@@ -954,13 +1118,15 @@
                 final AutofillId id = ids.get(i);
                 final AutofillValue value = values.get(i);
                 final RemoteViews fieldPresentation = presentations.get(i);
+                final RemoteViews fieldDialogPresentation = dialogPresentations.get(i);
                 final InlinePresentation fieldInlinePresentation =
                         i < inlinePresentationsSize ? inlinePresentations.get(i) : null;
                 final InlinePresentation fieldInlineTooltipPresentation =
                         i < inlinePresentationsSize ? inlineTooltipPresentations.get(i) : null;
                 final DatasetFieldFilter filter = filters.get(i);
                 builder.setLifeTheUniverseAndEverything(id, value, fieldPresentation,
-                        fieldInlinePresentation, fieldInlineTooltipPresentation, filter);
+                        fieldInlinePresentation, fieldInlineTooltipPresentation, filter,
+                        fieldDialogPresentation);
             }
             builder.setAuthentication(authentication);
             builder.setId(datasetId);
@@ -986,7 +1152,7 @@
         @Nullable
         public final Pattern pattern;
 
-        private DatasetFieldFilter(@Nullable Pattern pattern) {
+        DatasetFieldFilter(@Nullable Pattern pattern) {
             this.pattern = pattern;
         }
 
diff --git a/core/java/android/service/autofill/Field.java b/core/java/android/service/autofill/Field.java
new file mode 100644
index 0000000..b7c0d82
--- /dev/null
+++ b/core/java/android/service/autofill/Field.java
@@ -0,0 +1,165 @@
+/*
+ * 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.service.autofill;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+
+import com.android.internal.util.DataClass;
+
+import java.util.regex.Pattern;
+
+/**
+ * This class is used to set all information of a field. Such as the
+ * {@link AutofillId} of the field, the {@link AutofillValue} to be autofilled,
+ * a <a href="#Filtering">explicit filter</a>, and presentations to be visualized,
+ * etc.
+ */
+public final class Field {
+
+    /**
+     * The value to be autofilled. Pass {@code null} if you do not have the value
+     * but the target view is a logical part of the dataset. For example, if the
+     * dataset needs authentication and you have no access to the value.
+     */
+    private @Nullable AutofillValue mValue;
+
+    /**
+     * Regex used to determine if the dataset should be shown in the autofill UI;
+     * when {@code null}, it disables filtering on that dataset (this is the recommended
+     * approach when {@code value} is not {@code null} and field contains sensitive data
+     * such as passwords).
+     *
+     * @see Dataset.DatasetFieldFilter
+     * @hide
+     */
+    private @Nullable Dataset.DatasetFieldFilter mFilter;
+
+    /**
+     * The presentations used to visualize this field in Autofill UI.
+     */
+    private @Nullable Presentations mPresentations;
+
+
+    /* package-private */ Field(
+            @Nullable AutofillValue value,
+            @Nullable Dataset.DatasetFieldFilter filter,
+            @Nullable Presentations presentations) {
+        this.mValue = value;
+        this.mFilter = filter;
+        this.mPresentations = presentations;
+    }
+
+    /**
+     * The value to be autofilled. Pass {@code null} if you do not have the value
+     * but the target view is a logical part of the dataset. For example, if the
+     * dataset needs authentication and you have no access to the value.
+     */
+    @DataClass.Generated.Member
+    public @Nullable AutofillValue getValue() {
+        return mValue;
+    }
+
+    /**
+     * Regex used to determine if the dataset should be shown in the autofill UI;
+     * when {@code null}, it disables filtering on that dataset (this is the recommended
+     * approach when {@code value} is not {@code null} and field contains sensitive data
+     * such as passwords).
+     *
+     * @see Dataset.DatasetFieldFilter
+     * @hide
+     */
+    public @Nullable Dataset.DatasetFieldFilter getFilter() {
+        return mFilter;
+    }
+
+    /**
+     * The presentations used to visualize this field in Autofill UI.
+     */
+    public @Nullable Presentations getPresentations() {
+        return mPresentations;
+    }
+
+    /**
+     * A builder for {@link Field}
+     */
+    public static final class Builder {
+
+        private @Nullable AutofillValue mValue = null;
+        private @Nullable Dataset.DatasetFieldFilter mFilter = null;
+        private @Nullable Presentations mPresentations = null;
+        private boolean mDestroyed = false;
+
+        public Builder() {
+        }
+
+        /**
+         * The value to be autofilled. Pass {@code null} if you do not have the value
+         * but the target view is a logical part of the dataset. For example, if the
+         * dataset needs authentication and you have no access to the value.
+         */
+        public @NonNull Builder setValue(@NonNull AutofillValue value) {
+            checkNotUsed();
+            mValue = value;
+            return this;
+        }
+
+        /**
+         * Regex used to determine if the dataset should be shown in the autofill UI;
+         * when {@code null}, it disables filtering on that dataset (this is the recommended
+         * approach when {@code value} is not {@code null} and field contains sensitive data
+         * such as passwords).
+         */
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public @NonNull Builder setFilter(@Nullable Pattern value) {
+            checkNotUsed();
+            mFilter = new Dataset.DatasetFieldFilter(value);
+            return this;
+        }
+
+        /**
+         * The presentations used to visualize this field in Autofill UI.
+         */
+        public @NonNull Builder setPresentations(@NonNull Presentations value) {
+            checkNotUsed();
+            mPresentations = value;
+            return this;
+        }
+
+        /** Builds the instance. This builder should not be touched after calling this! */
+        public @NonNull Field build() {
+            checkNotUsed();
+            mDestroyed = true; // Mark builder used
+
+            Field o = new Field(
+                    mValue,
+                    mFilter,
+                    mPresentations);
+            return o;
+        }
+
+        private void checkNotUsed() {
+            if (mDestroyed) {
+                throw new IllegalStateException(
+                        "This Builder should not be reused. Use a new Builder instance instead");
+            }
+        }
+    }
+}
diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java
index 43bd410..e4d3732 100644
--- a/core/java/android/service/autofill/FillRequest.java
+++ b/core/java/android/service/autofill/FillRequest.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.IntentSender;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -98,6 +99,12 @@
 
     // The flag value 0x20 has been defined in AutofillManager.
 
+    /**
+     * Indicates the request is coming from the activity just started.
+     * @hide
+     */
+    public static final @RequestFlags int FLAG_ACTIVITY_START = 0x40;
+
     /** @hide */
     public static final int INVALID_REQUEST_ID = Integer.MIN_VALUE;
 
@@ -154,19 +161,32 @@
      */
     private final @Nullable InlineSuggestionsRequest mInlineSuggestionsRequest;
 
+    /**
+     * Gets the {@link IntentSender} to send a delayed fill response.
+     *
+     * <p>The autofill service must first indicate that it wants to return a delayed
+     * {@link FillResponse} by setting {@link FillResponse#FLAG_DELAY_FILL} in a successful
+     * fill response. Then it can use this IntentSender to send an Intent with extra
+     * {@link AutofillService#EXTRA_FILL_RESPONSE} with the delayed response.</p>
+     *
+     * <p>Note that this may be null if a delayed fill response is not supported for
+     * this fill request.</p>
+     */
+    private final @Nullable IntentSender mDelayedFillIntentSender;
+
     private void onConstructed() {
         Preconditions.checkCollectionElementsNotNull(mFillContexts, "contexts");
     }
 
 
 
-    // Code below generated by codegen v1.0.15.
+    // Code below generated by codegen v1.0.23.
     //
     // DO NOT MODIFY!
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/autofill/FillRequest.java
+    // $ codegen $ANDROID_BUILD_TOP/./frameworks/base/core/java/android/service/autofill/FillRequest.java
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -178,7 +198,8 @@
         FLAG_MANUAL_REQUEST,
         FLAG_COMPATIBILITY_MODE_REQUEST,
         FLAG_PASSWORD_INPUT_TYPE,
-        FLAG_VIEW_NOT_FOCUSED
+        FLAG_VIEW_NOT_FOCUSED,
+        FLAG_ACTIVITY_START
     })
     @Retention(RetentionPolicy.SOURCE)
     @DataClass.Generated.Member
@@ -202,6 +223,8 @@
                     return "FLAG_PASSWORD_INPUT_TYPE";
             case FLAG_VIEW_NOT_FOCUSED:
                     return "FLAG_VIEW_NOT_FOCUSED";
+            case FLAG_ACTIVITY_START:
+                    return "FLAG_ACTIVITY_START";
             default: return Integer.toHexString(value);
         }
     }
@@ -243,6 +266,16 @@
      *
      *   <p>The Autofill Service must set supportsInlineSuggestions in its XML to enable support
      *   for inline suggestions.</p>
+     * @param delayedFillIntentSender
+     *   Gets the {@link IntentSender} to send a delayed fill response.
+     *
+     *   <p>The autofill service must first indicate that it wants to return a delayed
+     *   {@link FillResponse} by setting {@link FillResponse#FLAG_DELAY_FILL} in a successful
+     *   fill response. Then it can use this IntentSender to send an Intent with extra
+     *   {@link AutofillService#EXTRA_FILL_RESPONSE} with the delayed response.</p>
+     *
+     *   <p>Note that this may be null if a delayed fill response is not supported for
+     *   this fill request.</p>
      * @hide
      */
     @DataClass.Generated.Member
@@ -251,7 +284,8 @@
             @NonNull List<FillContext> fillContexts,
             @Nullable Bundle clientState,
             @RequestFlags int flags,
-            @Nullable InlineSuggestionsRequest inlineSuggestionsRequest) {
+            @Nullable InlineSuggestionsRequest inlineSuggestionsRequest,
+            @Nullable IntentSender delayedFillIntentSender) {
         this.mId = id;
         this.mFillContexts = fillContexts;
         com.android.internal.util.AnnotationValidations.validate(
@@ -264,8 +298,10 @@
                 FLAG_MANUAL_REQUEST
                         | FLAG_COMPATIBILITY_MODE_REQUEST
                         | FLAG_PASSWORD_INPUT_TYPE
-                        | FLAG_VIEW_NOT_FOCUSED);
+                        | FLAG_VIEW_NOT_FOCUSED
+                        | FLAG_ACTIVITY_START);
         this.mInlineSuggestionsRequest = inlineSuggestionsRequest;
+        this.mDelayedFillIntentSender = delayedFillIntentSender;
 
         onConstructed();
     }
@@ -338,6 +374,22 @@
         return mInlineSuggestionsRequest;
     }
 
+    /**
+     * Gets the {@link IntentSender} to send a delayed fill response.
+     *
+     * <p>The autofill service must first indicate that it wants to return a delayed
+     * {@link FillResponse} by setting {@link FillResponse#FLAG_DELAY_FILL} in a successful
+     * fill response. Then it can use this IntentSender to send an Intent with extra
+     * {@link AutofillService#EXTRA_FILL_RESPONSE} with the delayed response.</p>
+     *
+     * <p>Note that this may be null if a delayed fill response is not supported for
+     * this fill request.</p>
+     */
+    @DataClass.Generated.Member
+    public @Nullable IntentSender getDelayedFillIntentSender() {
+        return mDelayedFillIntentSender;
+    }
+
     @Override
     @DataClass.Generated.Member
     public String toString() {
@@ -349,7 +401,8 @@
                 "fillContexts = " + mFillContexts + ", " +
                 "clientState = " + mClientState + ", " +
                 "flags = " + requestFlagsToString(mFlags) + ", " +
-                "inlineSuggestionsRequest = " + mInlineSuggestionsRequest +
+                "inlineSuggestionsRequest = " + mInlineSuggestionsRequest + ", " +
+                "delayedFillIntentSender = " + mDelayedFillIntentSender +
         " }";
     }
 
@@ -362,12 +415,14 @@
         byte flg = 0;
         if (mClientState != null) flg |= 0x4;
         if (mInlineSuggestionsRequest != null) flg |= 0x10;
+        if (mDelayedFillIntentSender != null) flg |= 0x20;
         dest.writeByte(flg);
         dest.writeInt(mId);
         dest.writeParcelableList(mFillContexts, flags);
         if (mClientState != null) dest.writeBundle(mClientState);
         dest.writeInt(mFlags);
         if (mInlineSuggestionsRequest != null) dest.writeTypedObject(mInlineSuggestionsRequest, flags);
+        if (mDelayedFillIntentSender != null) dest.writeTypedObject(mDelayedFillIntentSender, flags);
     }
 
     @Override
@@ -384,10 +439,11 @@
         byte flg = in.readByte();
         int id = in.readInt();
         List<FillContext> fillContexts = new ArrayList<>();
-        in.readParcelableList(fillContexts, FillContext.class.getClassLoader(), android.service.autofill.FillContext.class);
+        in.readParcelableList(fillContexts, FillContext.class.getClassLoader());
         Bundle clientState = (flg & 0x4) == 0 ? null : in.readBundle();
         int flags = in.readInt();
         InlineSuggestionsRequest inlineSuggestionsRequest = (flg & 0x10) == 0 ? null : (InlineSuggestionsRequest) in.readTypedObject(InlineSuggestionsRequest.CREATOR);
+        IntentSender delayedFillIntentSender = (flg & 0x20) == 0 ? null : (IntentSender) in.readTypedObject(IntentSender.CREATOR);
 
         this.mId = id;
         this.mFillContexts = fillContexts;
@@ -401,8 +457,10 @@
                 FLAG_MANUAL_REQUEST
                         | FLAG_COMPATIBILITY_MODE_REQUEST
                         | FLAG_PASSWORD_INPUT_TYPE
-                        | FLAG_VIEW_NOT_FOCUSED);
+                        | FLAG_VIEW_NOT_FOCUSED
+                        | FLAG_ACTIVITY_START);
         this.mInlineSuggestionsRequest = inlineSuggestionsRequest;
+        this.mDelayedFillIntentSender = delayedFillIntentSender;
 
         onConstructed();
     }
@@ -422,10 +480,10 @@
     };
 
     @DataClass.Generated(
-            time = 1589280816805L,
-            codegenVersion = "1.0.15",
+            time = 1643386870464L,
+            codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/service/autofill/FillRequest.java",
-            inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_NOT_FOCUSED\npublic static final  int INVALID_REQUEST_ID\nprivate final  int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate  void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
+            inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_NOT_FOCUSED\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_ACTIVITY_START\npublic static final  int INVALID_REQUEST_ID\nprivate final  int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate final @android.annotation.Nullable android.content.IntentSender mDelayedFillIntentSender\nprivate  void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index d94988e..296877a 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -65,10 +65,22 @@
      */
     public static final int FLAG_DISABLE_ACTIVITY_ONLY = 0x2;
 
+    /**
+     * Flag used to request to wait for a delayed fill from the remote Autofill service if it's
+     * passed to {@link Builder#setFlags(int)}.
+     *
+     * <p>Some datasets (i.e. OTP) take time to produce. This flags allows remote service to send
+     * a {@link FillResponse} to the latest {@link FillRequest} via
+     * {@link FillRequest#getDelayedFillIntentSender()} even if the original {@link FillCallback}
+     * has timed out.
+     */
+    public static final int FLAG_DELAY_FILL = 0x4;
+
     /** @hide */
     @IntDef(flag = true, prefix = { "FLAG_" }, value = {
             FLAG_TRACK_CONTEXT_COMMITED,
-            FLAG_DISABLE_ACTIVITY_ONLY
+            FLAG_DISABLE_ACTIVITY_ONLY,
+            FLAG_DELAY_FILL
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface FillResponseFlags {}
@@ -79,11 +91,14 @@
     private final @Nullable RemoteViews mPresentation;
     private final @Nullable InlinePresentation mInlinePresentation;
     private final @Nullable InlinePresentation mInlineTooltipPresentation;
+    private final @Nullable RemoteViews mDialogPresentation;
+    private final @Nullable RemoteViews mDialogHeader;
     private final @Nullable RemoteViews mHeader;
     private final @Nullable RemoteViews mFooter;
     private final @Nullable IntentSender mAuthentication;
     private final @Nullable AutofillId[] mAuthenticationIds;
     private final @Nullable AutofillId[] mIgnoredIds;
+    private final @Nullable AutofillId[] mFillDialogTriggerIds;
     private final long mDisableDuration;
     private final @Nullable AutofillId[] mFieldClassificationIds;
     private final int mFlags;
@@ -99,10 +114,13 @@
         mPresentation = builder.mPresentation;
         mInlinePresentation = builder.mInlinePresentation;
         mInlineTooltipPresentation = builder.mInlineTooltipPresentation;
+        mDialogPresentation = builder.mDialogPresentation;
+        mDialogHeader = builder.mDialogHeader;
         mHeader = builder.mHeader;
         mFooter = builder.mFooter;
         mAuthentication = builder.mAuthentication;
         mAuthenticationIds = builder.mAuthenticationIds;
+        mFillDialogTriggerIds = builder.mFillDialogTriggerIds;
         mIgnoredIds = builder.mIgnoredIds;
         mDisableDuration = builder.mDisableDuration;
         mFieldClassificationIds = builder.mFieldClassificationIds;
@@ -144,6 +162,16 @@
     }
 
     /** @hide */
+    public @Nullable RemoteViews getDialogPresentation() {
+        return mDialogPresentation;
+    }
+
+    /** @hide */
+    public @Nullable RemoteViews getDialogHeader() {
+        return mDialogHeader;
+    }
+
+    /** @hide */
     public @Nullable RemoteViews getHeader() {
         return mHeader;
     }
@@ -164,6 +192,11 @@
     }
 
     /** @hide */
+    public @Nullable AutofillId[] getFillDialogTriggerIds() {
+        return mFillDialogTriggerIds;
+    }
+
+    /** @hide */
     public @Nullable AutofillId[] getIgnoredIds() {
         return mIgnoredIds;
     }
@@ -229,6 +262,8 @@
         private RemoteViews mPresentation;
         private InlinePresentation mInlinePresentation;
         private InlinePresentation mInlineTooltipPresentation;
+        private RemoteViews mDialogPresentation;
+        private RemoteViews mDialogHeader;
         private RemoteViews mHeader;
         private RemoteViews mFooter;
         private IntentSender mAuthentication;
@@ -236,6 +271,7 @@
         private AutofillId[] mIgnoredIds;
         private long mDisableDuration;
         private AutofillId[] mFieldClassificationIds;
+        private AutofillId[] mFillDialogTriggerIds;
         private int mFlags;
         private boolean mDestroyed;
         private UserData mUserData;
@@ -243,7 +279,7 @@
         private boolean mSupportsInlineSuggestions;
 
         /**
-         * Triggers a custom UI before before autofilling the screen with any data set in this
+         * Triggers a custom UI before autofilling the screen with any data set in this
          * response.
          *
          * <p><b>Note:</b> Although the name of this method suggests that it should be used just for
@@ -277,7 +313,7 @@
          * example a credit card whose CVV needs to be entered.
          *
          * <p>If you provide an authentication intent you must also provide a presentation
-         * which is used to visualize visualize the response for triggering the authentication
+         * which is used to visualize the response for triggering the authentication
          * flow.
          *
          * <p><b>Note:</b> Do not make the provided pending intent
@@ -306,7 +342,11 @@
          * {@link #setFooter(RemoteViews) footer} are already set for this builder.
          *
          * @see android.app.PendingIntent#getIntentSender()
+         * @deprecated Use
+         * {@link #setAuthentication(AutofillId[], IntentSender, Presentations)}
+         * instead.
          */
+        @Deprecated
         @NonNull
         public Builder setAuthentication(@NonNull AutofillId[] ids,
                 @Nullable IntentSender authentication, @Nullable RemoteViews presentation) {
@@ -327,7 +367,7 @@
         }
 
         /**
-         * Triggers a custom UI before before autofilling the screen with any data set in this
+         * Triggers a custom UI before autofilling the screen with any data set in this
          * response.
          *
          * <p><b>Note:</b> Although the name of this method suggests that it should be used just for
@@ -365,7 +405,11 @@
          * {@link #setFooter(RemoteViews) footer} are already set for this builder.
          *
          * @see android.app.PendingIntent#getIntentSender()
+         * @deprecated Use
+         * {@link #setAuthentication(AutofillId[], IntentSender, Presentations)}
+         * instead.
          */
+        @Deprecated
         @NonNull
         public Builder setAuthentication(@NonNull AutofillId[] ids,
                 @Nullable IntentSender authentication, @Nullable RemoteViews presentation,
@@ -374,13 +418,18 @@
         }
 
         /**
-         * Triggers a custom UI before before autofilling the screen with any data set in this
+         * Triggers a custom UI before autofilling the screen with any data set in this
          * response.
          *
          * <p>This method like
          * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews, InlinePresentation)}
          * but allows setting an {@link InlinePresentation} for the inline suggestion tooltip.
+         *
+         * @deprecated Use
+         * {@link #setAuthentication(AutofillId[], IntentSender, Presentations)}
+         * instead.
          */
+        @Deprecated
         @NonNull
         public Builder setAuthentication(@SuppressLint("ArrayReturn") @NonNull AutofillId[] ids,
                 @Nullable IntentSender authentication, @Nullable RemoteViews presentation,
@@ -388,6 +437,105 @@
                 @Nullable InlinePresentation inlineTooltipPresentation) {
             throwIfDestroyed();
             throwIfDisableAutofillCalled();
+            return setAuthentication(ids, authentication, presentation,
+                    inlinePresentation, inlineTooltipPresentation, null);
+        }
+
+        /**
+         * Triggers a custom UI before autofilling the screen with any data set in this
+         * response.
+         *
+         * <p><b>Note:</b> Although the name of this method suggests that it should be used just for
+         * authentication flow, it can be used for other advanced flows; see {@link AutofillService}
+         * for examples.
+         *
+         * <p>This is typically useful when a user interaction is required to unlock their
+         * data vault if you encrypt the data set labels and data set data. It is recommended
+         * to encrypt only the sensitive data and not the data set labels which would allow
+         * auth on the data set level leading to a better user experience. Note that if you
+         * use sensitive data as a label, for example an email address, then it should also
+         * be encrypted. The provided {@link android.app.PendingIntent intent} must be an
+         * {@link Activity} which implements your authentication flow. Also if you provide an auth
+         * intent you also need to specify the presentation view to be shown in the fill UI
+         * for the user to trigger your authentication flow.
+         *
+         * <p>When a user triggers autofill, the system launches the provided intent
+         * whose extras will have the
+         * {@link android.view.autofill.AutofillManager#EXTRA_ASSIST_STRUCTURE screen
+         * content} and your {@link android.view.autofill.AutofillManager#EXTRA_CLIENT_STATE
+         * client state}. Once you complete your authentication flow you should set the
+         * {@link Activity} result to {@link android.app.Activity#RESULT_OK} and set the
+         * {@link android.view.autofill.AutofillManager#EXTRA_AUTHENTICATION_RESULT} extra
+         * with the fully populated {@link FillResponse response} (or {@code null} if the screen
+         * cannot be autofilled).
+         *
+         * <p>For example, if you provided an empty {@link FillResponse response} because the
+         * user's data was locked and marked that the response needs an authentication then
+         * in the response returned if authentication succeeds you need to provide all
+         * available data sets some of which may need to be further authenticated, for
+         * example a credit card whose CVV needs to be entered.
+         *
+         * <p>If you provide an authentication intent you must also provide a presentation
+         * which is used to visualize the response for triggering the authentication
+         * flow.
+         *
+         * <p><b>Note:</b> Do not make the provided pending intent
+         * immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the
+         * platform needs to fill in the authentication arguments.
+         *
+         * <p><b>Note:</b> {@link #setHeader(RemoteViews)} or {@link #setFooter(RemoteViews)} does
+         * not work with {@link InlinePresentation}.</p>
+         *
+         * @param ids id of Views that when focused will display the authentication UI.
+         * @param authentication Intent to an activity with your authentication flow.
+         * @param presentations The presentations to visualize the response.
+         *
+         * @throws IllegalArgumentException if any of the following occurs:
+         * <ul>
+         *   <li>{@code ids} is {@code null}</li>
+         *   <li>{@code ids} is empty</li>
+         *   <li>{@code ids} contains a {@code null} element</li>
+         *   <li>{@code authentication} is {@code null}, but either or both of
+         *   {@code presentations.getPresentation()} and
+         *   {@code presentations.getInlinePresentation()} is non-{@code null}</li>
+         *   <li>{@code authentication} is non-{{@code null}, but both
+         *   {@code presentations.getPresentation()} and
+         *   {@code presentations.getInlinePresentation()} are {@code null}</li>
+         * </ul>
+         *
+         * @throws IllegalStateException if a {@link #setHeader(RemoteViews) header} or a
+         * {@link #setFooter(RemoteViews) footer} are already set for this builder.
+         *
+         * @return This builder.
+         */
+        @NonNull
+        public Builder setAuthentication(@SuppressLint("ArrayReturn") @NonNull AutofillId[] ids,
+                @Nullable IntentSender authentication,
+                @Nullable Presentations presentations) {
+            throwIfDestroyed();
+            throwIfDisableAutofillCalled();
+            if (presentations == null) {
+                return setAuthentication(ids, authentication, null, null, null, null);
+            }
+            return setAuthentication(ids, authentication,
+                    presentations.getMenuPresentation(),
+                    presentations.getInlinePresentation(),
+                    presentations.getInlineTooltipPresentation(),
+                    presentations.getDialogPresentation());
+        }
+
+        /**
+         * Triggers a custom UI before autofilling the screen with any data set in this
+         * response.
+         */
+        @NonNull
+        private Builder setAuthentication(@SuppressLint("ArrayReturn") @NonNull AutofillId[] ids,
+                @Nullable IntentSender authentication, @Nullable RemoteViews presentation,
+                @Nullable InlinePresentation inlinePresentation,
+                @Nullable InlinePresentation inlineTooltipPresentation,
+                @Nullable RemoteViews dialogPresentation) {
+            throwIfDestroyed();
+            throwIfDisableAutofillCalled();
             if (mHeader != null || mFooter != null) {
                 throw new IllegalStateException("Already called #setHeader() or #setFooter()");
             }
@@ -400,6 +548,7 @@
             mPresentation = presentation;
             mInlinePresentation = inlinePresentation;
             mInlineTooltipPresentation = inlineTooltipPresentation;
+            mDialogPresentation = dialogPresentation;
             mAuthenticationIds = assertValid(ids);
             return this;
         }
@@ -520,7 +669,7 @@
         public Builder setFlags(@FillResponseFlags int flags) {
             throwIfDestroyed();
             mFlags = Preconditions.checkFlagsArgument(flags,
-                    FLAG_TRACK_CONTEXT_COMMITED | FLAG_DISABLE_ACTIVITY_ONLY);
+                    FLAG_TRACK_CONTEXT_COMMITED | FLAG_DISABLE_ACTIVITY_ONLY | FLAG_DELAY_FILL);
             return this;
         }
 
@@ -552,7 +701,7 @@
          *
          * @throws IllegalArgumentException if {@code duration} is not a positive number.
          * @throws IllegalStateException if either {@link #addDataset(Dataset)},
-         *       {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)},
+         *       {@link #setAuthentication(AutofillId[], IntentSender, Presentations)},
          *       {@link #setSaveInfo(SaveInfo)}, {@link #setClientState(Bundle)}, or
          *       {@link #setFieldClassificationIds(AutofillId...)} was already called.
          */
@@ -591,8 +740,8 @@
          * @return this builder
          *
          * @throws IllegalStateException if an
-         * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews) authentication} was
-         * already set for this builder.
+         * {@link #setAuthentication(AutofillId[], IntentSender, Presentations)
+         * authentication} was already set for this builder.
          */
         // TODO(b/69796626): make it sticky / update javadoc
         @NonNull
@@ -623,7 +772,7 @@
          * @return this builder
          *
          * @throws IllegalStateException if the FillResponse
-         * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)
+         * {@link #setAuthentication(AutofillId[], IntentSender, Presentations)
          * requires authentication}.
          */
         // TODO(b/69796626): make it sticky / update javadoc
@@ -643,7 +792,7 @@
          *
          * @return this builder
          * @throws IllegalStateException if the FillResponse
-         * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)
+         * {@link #setAuthentication(AutofillId[], IntentSender, Presentations)
          * requires authentication}.
          */
         @NonNull
@@ -674,13 +823,46 @@
         }
 
         /**
+         * Sets the presentation of header in fill dialog UI. The header should have
+         * a prompt for what datasets are shown in the dialog. If this is not set,
+         * the dialog only shows your application icon.
+         *
+         * More details about the fill dialog, see
+         * <a href="Dataset.html#FillDialogUI">fill dialog UI</a>
+         */
+        @NonNull
+        public Builder setDialogHeader(@NonNull RemoteViews header) {
+            throwIfDestroyed();
+            Objects.requireNonNull(header);
+            mDialogHeader = header;
+            return this;
+        }
+
+        /**
+         * Sets which fields are used for the fill dialog UI.
+         *
+         * More details about the fill dialog, see
+         * <a href="Dataset.html#FillDialogUI">fill dialog UI</a>
+         *
+         * @throws IllegalStateException if {@link #build()} was already called.
+         * @throws NullPointerException if {@code ids} or any element on it is {@code null}.
+         */
+        @NonNull
+        public Builder setFillDialogTriggerIds(@NonNull AutofillId... ids) {
+            throwIfDestroyed();
+            Preconditions.checkArrayElementsNotNull(ids, "ids");
+            mFillDialogTriggerIds = ids;
+            return this;
+        }
+
+        /**
          * Builds a new {@link FillResponse} instance.
          *
          * @throws IllegalStateException if any of the following conditions occur:
          * <ol>
          *   <li>{@link #build()} was already called.
          *   <li>No call was made to {@link #addDataset(Dataset)},
-         *       {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)},
+         *       {@link #setAuthentication(AutofillId[], IntentSender, Presentations)},
          *       {@link #setSaveInfo(SaveInfo)}, {@link #disableAutofill(long)},
          *       {@link #setClientState(Bundle)},
          *       or {@link #setFieldClassificationIds(AutofillId...)}.
@@ -767,6 +949,12 @@
         if (mInlineTooltipPresentation != null) {
             builder.append(", hasInlineTooltipPresentation");
         }
+        if (mDialogPresentation != null) {
+            builder.append(", hasDialogPresentation");
+        }
+        if (mDialogHeader != null) {
+            builder.append(", hasDialogHeader");
+        }
         if (mHeader != null) {
             builder.append(", hasHeader");
         }
@@ -779,6 +967,10 @@
         if (mAuthenticationIds != null) {
             builder.append(", authenticationIds=").append(Arrays.toString(mAuthenticationIds));
         }
+        if (mFillDialogTriggerIds != null) {
+            builder.append(", fillDialogTriggerIds=")
+                    .append(Arrays.toString(mFillDialogTriggerIds));
+        }
         builder.append(", disableDuration=").append(mDisableDuration);
         if (mFlags != 0) {
             builder.append(", flags=").append(mFlags);
@@ -815,6 +1007,9 @@
         parcel.writeParcelable(mPresentation, flags);
         parcel.writeParcelable(mInlinePresentation, flags);
         parcel.writeParcelable(mInlineTooltipPresentation, flags);
+        parcel.writeParcelable(mDialogPresentation, flags);
+        parcel.writeParcelable(mDialogHeader, flags);
+        parcel.writeParcelableArray(mFillDialogTriggerIds, flags);
         parcel.writeParcelable(mHeader, flags);
         parcel.writeParcelable(mFooter, flags);
         parcel.writeParcelable(mUserData, flags);
@@ -850,9 +1045,18 @@
             final RemoteViews presentation = parcel.readParcelable(null, android.widget.RemoteViews.class);
             final InlinePresentation inlinePresentation = parcel.readParcelable(null, android.service.autofill.InlinePresentation.class);
             final InlinePresentation inlineTooltipPresentation = parcel.readParcelable(null, android.service.autofill.InlinePresentation.class);
+            final RemoteViews dialogPresentation = parcel.readParcelable(null, android.widget.RemoteViews.class);
             if (authenticationIds != null) {
                 builder.setAuthentication(authenticationIds, authentication, presentation,
-                        inlinePresentation, inlineTooltipPresentation);
+                        inlinePresentation, inlineTooltipPresentation, dialogPresentation);
+            }
+            final RemoteViews dialogHeader = parcel.readParcelable(null, android.widget.RemoteViews.class);
+            if (dialogHeader != null) {
+                builder.setDialogHeader(dialogHeader);
+            }
+            final AutofillId[] triggerIds = parcel.readParcelableArray(null, AutofillId.class);
+            if (triggerIds != null) {
+                builder.setFillDialogTriggerIds(triggerIds);
             }
             final RemoteViews header = parcel.readParcelable(null, android.widget.RemoteViews.class);
             if (header != null) {
diff --git a/core/java/android/service/autofill/Presentations.java b/core/java/android/service/autofill/Presentations.java
new file mode 100644
index 0000000..e8ac628
--- /dev/null
+++ b/core/java/android/service/autofill/Presentations.java
@@ -0,0 +1,278 @@
+/*
+ * 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.service.autofill;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.widget.RemoteViews;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * Holds presentations used to visualize autofill suggestions for each available UI type.
+ *
+ * @see Field
+ */
+@DataClass(genBuilder = true)
+public final class Presentations {
+
+    /**
+     * The presentation used to visualize this field in fill UI.
+     *
+     * <p>Note: Before Android 13, this was referred to simply as "presentation" in the SDK.
+     *
+     * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color
+     * or background color: Autofill on different platforms may have different themes.
+     */
+    private @Nullable RemoteViews mMenuPresentation;
+
+    /**
+     * The {@link InlinePresentation} used to visualize this dataset as inline suggestions.
+     * If the dataset supports inline suggestions, this should not be null.
+     */
+    private @Nullable InlinePresentation mInlinePresentation;
+
+    /**
+     * The presentation used to visualize this field in the
+     * <a href="Dataset.html#FillDialogUI">fill dialog UI</a>
+     *
+     * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color
+     * or background color: Autofill on different platforms may have different themes.
+     */
+    private @Nullable RemoteViews mDialogPresentation;
+
+    /**
+     * The {@link InlinePresentation} used to show the tooltip for the
+     * {@code mInlinePresentation}. If the set this field, the
+     * {@code mInlinePresentation} should not be null.
+     */
+    private @Nullable InlinePresentation mInlineTooltipPresentation;
+
+    private static RemoteViews defaultMenuPresentation() {
+        return null;
+    }
+
+    private static InlinePresentation defaultInlinePresentation() {
+        return null;
+    }
+
+    private static RemoteViews defaultDialogPresentation() {
+        return null;
+    }
+
+    private static InlinePresentation defaultInlineTooltipPresentation() {
+        return null;
+    }
+
+    private void onConstructed() {
+        if (mMenuPresentation == null
+                && mInlinePresentation == null
+                && mDialogPresentation == null) {
+            throw new IllegalStateException("All presentations are null.");
+        }
+        if (mInlineTooltipPresentation != null && mInlinePresentation == null) {
+            throw new IllegalStateException(
+                    "The inline presentation is required for mInlineTooltipPresentation.");
+        }
+    }
+
+
+
+    // Code below generated by codegen v1.0.23.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/./frameworks/base/core/java/android/service/autofill/Presentations.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    @DataClass.Generated.Member
+    /* package-private */ Presentations(
+            @Nullable RemoteViews menuPresentation,
+            @Nullable InlinePresentation inlinePresentation,
+            @Nullable RemoteViews dialogPresentation,
+            @Nullable InlinePresentation inlineTooltipPresentation) {
+        this.mMenuPresentation = menuPresentation;
+        this.mInlinePresentation = inlinePresentation;
+        this.mDialogPresentation = dialogPresentation;
+        this.mInlineTooltipPresentation = inlineTooltipPresentation;
+
+        onConstructed();
+    }
+
+    /**
+     * The presentation used to visualize this field in fill UI.
+     *
+     * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color
+     * or background color: Autofill on different platforms may have different themes.
+     */
+    @DataClass.Generated.Member
+    public @Nullable RemoteViews getMenuPresentation() {
+        return mMenuPresentation;
+    }
+
+    /**
+     * The {@link InlinePresentation} used to visualize this dataset as inline suggestions.
+     * If the dataset supports inline suggestions, this should not be null.
+     */
+    @DataClass.Generated.Member
+    public @Nullable InlinePresentation getInlinePresentation() {
+        return mInlinePresentation;
+    }
+
+    /**
+     * The presentation used to visualize this field in the fill dialog UI.
+     *
+     * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color
+     * or background color: Autofill on different platforms may have different themes.
+     */
+    @DataClass.Generated.Member
+    public @Nullable RemoteViews getDialogPresentation() {
+        return mDialogPresentation;
+    }
+
+    /**
+     * The {@link InlinePresentation} used to show the tooltip for the
+     * {@code mInlinePresentation}. If the set this field, the
+     * {@code mInlinePresentation} should not be null.
+     */
+    @DataClass.Generated.Member
+    public @Nullable InlinePresentation getInlineTooltipPresentation() {
+        return mInlineTooltipPresentation;
+    }
+
+    /**
+     * A builder for {@link Presentations}
+     */
+    @SuppressWarnings("WeakerAccess")
+    @DataClass.Generated.Member
+    public static final class Builder {
+
+        private @Nullable RemoteViews mMenuPresentation;
+        private @Nullable InlinePresentation mInlinePresentation;
+        private @Nullable RemoteViews mDialogPresentation;
+        private @Nullable InlinePresentation mInlineTooltipPresentation;
+
+        private long mBuilderFieldsSet = 0L;
+
+        public Builder() {
+        }
+
+        /**
+         * The presentation used to visualize this field in fill UI.
+         *
+         * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color
+         * or background color: Autofill on different platforms may have different themes.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setMenuPresentation(@NonNull RemoteViews value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x1;
+            mMenuPresentation = value;
+            return this;
+        }
+
+        /**
+         * The {@link InlinePresentation} used to visualize this dataset as inline suggestions.
+         * If the dataset supports inline suggestions, this should not be null.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setInlinePresentation(@NonNull InlinePresentation value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x2;
+            mInlinePresentation = value;
+            return this;
+        }
+
+        /**
+         * The presentation used to visualize this field in the fill dialog UI.
+         *
+         * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color
+         * or background color: Autofill on different platforms may have different themes.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setDialogPresentation(@NonNull RemoteViews value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x4;
+            mDialogPresentation = value;
+            return this;
+        }
+
+        /**
+         * The {@link InlinePresentation} used to show the tooltip for the
+         * {@code mInlinePresentation}. If the set this field, the
+         * {@code mInlinePresentation} should not be null.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setInlineTooltipPresentation(@NonNull InlinePresentation value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x8;
+            mInlineTooltipPresentation = value;
+            return this;
+        }
+
+        /** Builds the instance. This builder should not be touched after calling this! */
+        public @NonNull Presentations build() {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x10; // Mark builder used
+
+            if ((mBuilderFieldsSet & 0x1) == 0) {
+                mMenuPresentation = defaultMenuPresentation();
+            }
+            if ((mBuilderFieldsSet & 0x2) == 0) {
+                mInlinePresentation = defaultInlinePresentation();
+            }
+            if ((mBuilderFieldsSet & 0x4) == 0) {
+                mDialogPresentation = defaultDialogPresentation();
+            }
+            if ((mBuilderFieldsSet & 0x8) == 0) {
+                mInlineTooltipPresentation = defaultInlineTooltipPresentation();
+            }
+            Presentations o = new Presentations(
+                    mMenuPresentation,
+                    mInlinePresentation,
+                    mDialogPresentation,
+                    mInlineTooltipPresentation);
+            return o;
+        }
+
+        private void checkNotUsed() {
+            if ((mBuilderFieldsSet & 0x10) != 0) {
+                throw new IllegalStateException(
+                        "This Builder should not be reused. Use a new Builder instance instead");
+            }
+        }
+    }
+
+    @DataClass.Generated(
+            time = 1643083242164L,
+            codegenVersion = "1.0.23",
+            sourceFile = "frameworks/base/core/java/android/service/autofill/Presentations.java",
+            inputSignatures = "private @android.annotation.Nullable android.widget.RemoteViews mMenuPresentation\nprivate @android.annotation.Nullable android.service.autofill.InlinePresentation mInlinePresentation\nprivate @android.annotation.Nullable android.widget.RemoteViews mDialogPresentation\nprivate @android.annotation.Nullable android.service.autofill.InlinePresentation mInlineTooltipPresentation\nprivate static  android.widget.RemoteViews defaultMenuPresentation()\nprivate static  android.service.autofill.InlinePresentation defaultInlinePresentation()\nprivate static  android.widget.RemoteViews defaultDialogPresentation()\nprivate static  android.service.autofill.InlinePresentation defaultInlineTooltipPresentation()\nprivate  void onConstructed()\nclass Presentations extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl b/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl
index 31352f1..11e5ad8 100644
--- a/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl
+++ b/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl
@@ -37,5 +37,6 @@
     boolean getOemUnlockEnabled();
     int getFlashLockState();
     boolean hasFrpCredentialHandle();
+    String getPersistentDataPackageName();
 }
 
diff --git a/core/java/android/service/persistentdata/PersistentDataBlockManager.java b/core/java/android/service/persistentdata/PersistentDataBlockManager.java
index 44a8862..9167153 100644
--- a/core/java/android/service/persistentdata/PersistentDataBlockManager.java
+++ b/core/java/android/service/persistentdata/PersistentDataBlockManager.java
@@ -26,8 +26,6 @@
 import android.os.RemoteException;
 import android.service.oemlock.OemLockManager;
 
-import com.android.internal.R;
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
@@ -53,7 +51,6 @@
 @SystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE)
 public class PersistentDataBlockManager {
     private static final String TAG = PersistentDataBlockManager.class.getSimpleName();
-    private final Context mContext;
     private IPersistentDataBlockService sService;
 
     /**
@@ -78,10 +75,7 @@
     public @interface FlashLockState {}
 
     /** @hide */
-    public PersistentDataBlockManager(
-            Context context,
-            IPersistentDataBlockService service) {
-        mContext = context;
+    public PersistentDataBlockManager(IPersistentDataBlockService service) {
         sService = service;
     }
 
@@ -219,7 +213,12 @@
      */
     @SystemApi
     @NonNull
+    @RequiresPermission(android.Manifest.permission.ACCESS_PDB_STATE)
     public String getPersistentDataPackageName() {
-        return mContext.getString(R.string.config_persistentDataPackageName);
+        try {
+            return sService.getPersistentDataPackageName();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 }
diff --git a/core/java/android/service/tracing/TraceReportService.java b/core/java/android/service/tracing/TraceReportService.java
new file mode 100644
index 0000000..3d16a3d
--- /dev/null
+++ b/core/java/android/service/tracing/TraceReportService.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.tracing;
+
+import static android.annotation.SystemApi.Client.PRIVILEGED_APPS;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.tracing.TraceReportParams;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.UUID;
+
+/**
+ * Service to be sub-classed and exposed by (privileged) apps which want to report
+ * system traces.
+ * <p>
+ * Subclasses should implement the onReportTrace method to handle traces reported
+ * to them.
+ * </p>
+ * <pre>
+ *    public class SampleReportService extends TraceReportService {
+ *        public void onReportTrace(TraceParams args) {
+ *            // --- Implementation goes here ---
+ *        }
+ *    }
+ * </pre>
+ * <p>
+ * The service declaration in the application manifest must specify
+ * BIND_TRACE_REPORT_SERVICE in the permission attribute.
+ * </p>
+ * <pre>
+ *   &lt;application>
+ *        &lt;service android:name=".SampleReportService"
+ *               android:permission="android.permission.BIND_TRACE_REPORT_SERVICE">
+ *       &lt;/service>
+ *   &lt;/application>
+ * </pre>
+ *
+ * Moreover, the package containing this service must hold the DUMP and PACKAGE_USAGE_STATS
+ * permissions.
+ *
+ * @hide
+ */
+@SystemApi(client = PRIVILEGED_APPS)
+public class TraceReportService extends Service {
+    private static final String TAG = "TraceReportService";
+    private Messenger mMessenger = null;
+
+    /**
+     * Public to allow this to be used by TracingServiceProxy in system_server.
+     *
+     * @hide
+     */
+    public static final int MSG_REPORT_TRACE = 1;
+
+    /**
+     * Contains information about the trace which is being reported.
+     *
+     * @hide
+     */
+    @SystemApi(client = PRIVILEGED_APPS)
+    public static final class TraceParams {
+        private final ParcelFileDescriptor mFd;
+        private final UUID mUuid;
+
+        private TraceParams(TraceReportParams params) {
+            mFd = params.fd;
+            mUuid = new UUID(params.uuidMsb, params.uuidLsb);
+        }
+
+        /**
+         * Returns the ParcelFileDescriptor for the collected trace.
+         */
+        @NonNull
+        public ParcelFileDescriptor getFd() {
+            return mFd;
+        }
+
+        /**
+         * Returns the UUID of the trace; this is exactly the UUID created by the tracing system
+         * (i.e. Perfetto) and is also present inside the trace file.
+         */
+        @NonNull
+        public UUID getUuid() {
+            return mUuid;
+        }
+    }
+
+    // Methods to override.
+    /**
+     * Called when a trace is reported and sent to this class.
+     *
+     * Note: the trace file descriptor should not be persisted beyond the lifetime of this
+     * function as it is owned by the framework and will be closed immediately after this function
+     * returns: if future use of the fd is needed, it should be duped.
+     */
+    public void onReportTrace(@NonNull TraceParams args) {
+    }
+
+    // Optional methods to override.
+    // Realistically, these methods are internal implementation details but since this class is
+    // a SystemApi, it's better to err on the side of flexibility just in-case we need to override
+    // these methods down the line.
+
+    /**
+     * Handles binder calls from system_server.
+     */
+    public boolean onMessage(@NonNull Message msg) {
+        if (msg.what == MSG_REPORT_TRACE) {
+            if (!(msg.obj instanceof TraceReportParams)) {
+                Log.e(TAG, "Received invalid type for report trace message.");
+                return false;
+            }
+            TraceParams params = new TraceParams((TraceReportParams) msg.obj);
+            try {
+                onReportTrace(params);
+            } finally {
+                try {
+                    params.getFd().close();
+                } catch (IOException ignored) {
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns an IBinder for handling binder calls from system_server.
+     */
+    @Nullable
+    @Override
+    public IBinder onBind(@NonNull Intent intent) {
+        if (mMessenger == null) {
+            mMessenger = new Messenger(new Handler(Looper.getMainLooper(), this::onMessage));
+        }
+        return mMessenger.getBinder();
+    }
+}
\ No newline at end of file
diff --git a/core/java/android/service/trust/TrustAgentService.java b/core/java/android/service/trust/TrustAgentService.java
index fba61cf..5bd4235 100644
--- a/core/java/android/service/trust/TrustAgentService.java
+++ b/core/java/android/service/trust/TrustAgentService.java
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.annotation.SdkConstant;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.app.Service;
 import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
@@ -310,9 +311,10 @@
      *
      * @see #FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE
      *
-     * TODO(b/213631672): Add CTS tests
+     * TODO(b/213631672): Remove @hide and @TestApi
      * @hide
      */
+    @TestApi
     public void onUserRequestedUnlock() {
     }
 
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index f2a0355..c91851a 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -905,11 +905,12 @@
             if (!ENABLE_WALLPAPER_DIMMING || mBbqSurfaceControl == null) {
                 return;
             }
+
+            SurfaceControl.Transaction surfaceControlTransaction = new SurfaceControl.Transaction();
             // TODO: apply the dimming to preview as well once surface transparency works in
             // preview mode.
             if (!isPreview() && mShouldDim) {
                 Log.v(TAG, "Setting wallpaper dimming: " + mWallpaperDimAmount);
-                SurfaceControl.Transaction surfaceControl = new SurfaceControl.Transaction();
 
                 // Animate dimming to gradually change the wallpaper alpha from the previous
                 // dim amount to the new amount only if the dim amount changed.
@@ -919,16 +920,15 @@
                         ? 0 : DIMMING_ANIMATION_DURATION_MS);
                 animator.addUpdateListener((ValueAnimator va) -> {
                     final float dimValue = (float) va.getAnimatedValue();
-                    surfaceControl
-                            .setAlpha(mBbqSurfaceControl, 1 - dimValue)
-                            .apply();
+                    if (mBbqSurfaceControl != null) {
+                        surfaceControlTransaction
+                                .setAlpha(mBbqSurfaceControl, 1 - dimValue).apply();
+                    }
                 });
                 animator.start();
             } else {
                 Log.v(TAG, "Setting wallpaper dimming: " + 0);
-                new SurfaceControl.Transaction()
-                        .setAlpha(mBbqSurfaceControl, 1.0f)
-                        .apply();
+                surfaceControlTransaction.setAlpha(mBbqSurfaceControl, 1.0f).apply();
             }
         }
 
diff --git a/core/java/android/tracing/ITracingServiceProxy.aidl b/core/java/android/tracing/ITracingServiceProxy.aidl
index 4520db3..8029b88 100644
--- a/core/java/android/tracing/ITracingServiceProxy.aidl
+++ b/core/java/android/tracing/ITracingServiceProxy.aidl
@@ -16,17 +16,25 @@
 
 package android.tracing;
 
+import android.tracing.TraceReportParams;
+
 /**
  * Binder interface for the TracingServiceProxy running in system_server.
  *
  * {@hide}
  */
-interface ITracingServiceProxy
-{
+interface ITracingServiceProxy {
     /**
      * Notifies system tracing app that a tracing session has ended. If a session is repurposed
      * for use in a bugreport, sessionStolen can be set to indicate that tracing has ended but
      * there is no buffer available to dump.
      */
     oneway void notifyTraceSessionEnded(boolean sessionStolen);
+
+    /**
+     * Notifies the specified service that a trace has been captured. The contents of |params|
+     * contains the intended recipient (package and class) of this trace as well as a file
+     * descriptor to an unlinked trace |fd| (i.e. an fd opened using O_TMPFILE).
+     */
+    oneway void reportTrace(in TraceReportParams params);
 }
diff --git a/core/java/android/tracing/TraceReportParams.aidl b/core/java/android/tracing/TraceReportParams.aidl
new file mode 100644
index 0000000..f57386c
--- /dev/null
+++ b/core/java/android/tracing/TraceReportParams.aidl
@@ -0,0 +1,59 @@
+/**
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.tracing;
+
+import android.os.ParcelFileDescriptor;
+
+/*
+ * Parameters for a trace report.
+ *
+ * See ITracingServiceProxy::reportTrace for more details.
+ *
+ * @hide
+ */
+parcelable TraceReportParams {
+  // The package name containing the reporter service (see |reporterClassName|).
+  String reporterPackageName;
+
+  // The class name of the reporter service. The framework will bind to this service and pass the
+  // trace fd and metadata to this class.
+  // This class should be "trusted" (in practice this means being a priv_app + having DUMP and
+  // USAGE_STATS permissions).
+  String reporterClassName;
+
+  // The file descriptor for the trace file. This will be an unlinked file fd (i.e. created
+  // with O_TMPFILE); the intention is that reporter classes link this fd into a app-private
+  // folder for reporting when conditions are right (e.g. charging, on unmetered networks etc).
+  ParcelFileDescriptor fd;
+
+  // The least-significant-bytes of the UUID of this trace.
+  long uuidLsb;
+
+  // The most-significant-bytes of the UUID of this trace.
+  long uuidMsb;
+
+  // Flag indicating whether, instead of passing the fd from the trace collector, to pass a
+  // pipe fd from system_server and send the file over it.
+  //
+  // This flag is necessary because there is no good way to write a CTS test where a helper
+  // priv_app (in terms of SELinux) is needed (this is because priv_apps are supposed to be
+  // preinstalled on the system partition). By creating a pipe in system_server we work around
+  // this restriction. Note that there is a maximum allowed file size if this flag is set
+  // (see TracingServiceProxy). Further note that, even though SELinux may be worked around,
+  // manifest (i.e. framework) permissions are still checked even if this flag is set.
+  boolean usePipeForTesting;
+}
\ No newline at end of file
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 7559273..4ff7e229 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -8178,9 +8178,13 @@
                         // to User. Ideally View should handle the event when isVisibleToUser()
                         // becomes true where it should issue notifyViewEntered().
                         afm.notifyViewEntered(this);
+                    } else {
+                        afm.enableFillRequestActivityStarted();
                     }
                 } else if (!enter && !isFocused()) {
                     afm.notifyViewExited(this);
+                } else if (enter) {
+                    afm.enableFillRequestActivityStarted();
                 }
             }
         }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index eaa12e5..386b277 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -563,6 +563,8 @@
     @Nullable
     int mContentCaptureEnabled = CONTENT_CAPTURE_ENABLED_NOT_CHECKED;
     boolean mPerformContentCapture;
+    boolean mPerformAutoFill;
+
 
     boolean mReportNextDraw;
     /**
@@ -842,6 +844,7 @@
         mPreviousTransparentRegion = new Region();
         mFirst = true; // true for the first time the view is added
         mPerformContentCapture = true; // also true for the first time the view is added
+        mPerformAutoFill = true;
         mAdded = false;
         mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
                 context);
@@ -4353,6 +4356,18 @@
         if (mPerformContentCapture) {
             performContentCaptureInitialReport();
         }
+
+        if (mPerformAutoFill) {
+            notifyEnterForAutoFillIfNeeded();
+        }
+    }
+
+    private void notifyEnterForAutoFillIfNeeded() {
+        mPerformAutoFill = false;
+        final AutofillManager afm = getAutofillManager();
+        if (afm != null) {
+            afm.notifyViewEnteredForActivityStarted(mView);
+        }
     }
 
     /**
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index bb13c1e..60ccf67 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -16,6 +16,7 @@
 
 package android.view.autofill;
 
+import static android.service.autofill.FillRequest.FLAG_ACTIVITY_START;
 import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
 import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE;
 import static android.service.autofill.FillRequest.FLAG_VIEW_NOT_FOCUSED;
@@ -538,6 +539,8 @@
      */
     public static final int NO_SESSION = Integer.MAX_VALUE;
 
+    private static final boolean HAS_FILL_DIALOG_UI_FEATURE = false;
+
     private final IAutoFillManager mService;
 
     private final Object mLock = new Object();
@@ -629,6 +632,29 @@
     @GuardedBy("mLock")
     @Nullable private Executor mRequestCallbackExecutor;
 
+    /**
+     * Indicates whether there are any fields that need to do a fill request
+     * after the activity starts.
+     *
+     * Note: This field will be set to true multiple times if there are many
+     * autofillable views. So needs to check mIsFillRequested at the same time to
+     * avoid re-trigger autofill.
+     */
+    private boolean mRequireAutofill;
+
+    /**
+     * Indicates whether there is already a field to do a fill request after
+     * the activity started.
+     *
+     * Autofill will automatically trigger a fill request after activity
+     * start if there is any field is autofillable. But if there is a field that
+     * triggered autofill, it is unnecessary to trigger again through
+     * AutofillManager#notifyViewEnteredForActivityStarted.
+     */
+    private boolean mIsFillRequested;
+
+    @Nullable private List<AutofillId> mFillDialogTriggerIds;
+
     /** @hide */
     public interface AutofillClient {
         /**
@@ -766,6 +792,8 @@
         mContext = Objects.requireNonNull(context, "context cannot be null");
         mService = service;
         mOptions = context.getAutofillOptions();
+        mIsFillRequested = false;
+        mRequireAutofill = false;
 
         if (mOptions != null) {
             sDebug = (mOptions.loggingLevel & FLAG_ADD_CLIENT_DEBUG) != 0;
@@ -1042,6 +1070,39 @@
         notifyViewEntered(view, 0);
     }
 
+    /**
+     * The view is autofillable, marked to perform a fill request after layout if
+     * the field does not trigger a fill request.
+     *
+     * @hide
+     */
+    public void enableFillRequestActivityStarted() {
+        mRequireAutofill = true;
+    }
+
+    private boolean hasFillDialogUiFeature() {
+        return HAS_FILL_DIALOG_UI_FEATURE;
+    }
+
+    /**
+     * Notify autofill to do a fill request while the activity started.
+     *
+     * @hide
+     */
+    public void notifyViewEnteredForActivityStarted(@NonNull View view) {
+        if (!hasAutofillFeature() || !hasFillDialogUiFeature()) {
+            return;
+        }
+
+        if (!mRequireAutofill || mIsFillRequested) {
+            return;
+        }
+
+        int flags = FLAG_ACTIVITY_START;
+        flags |= FLAG_VIEW_NOT_FOCUSED;
+        notifyViewEntered(view, flags);
+    }
+
     @GuardedBy("mLock")
     private boolean shouldIgnoreViewEnteredLocked(@NonNull AutofillId id, int flags) {
         if (isDisabledByServiceLocked()) {
@@ -1082,6 +1143,7 @@
         }
         AutofillCallback callback;
         synchronized (mLock) {
+            mIsFillRequested = true;
             callback = notifyViewEnteredLocked(view, flags);
         }
 
@@ -2026,6 +2088,8 @@
         mFillableIds = null;
         mSaveTriggerId = null;
         mIdShownFillUi = null;
+        mIsFillRequested = false;
+        mRequireAutofill = false;
         if (resetEnteredIds) {
             mEnteredIds = null;
         }
@@ -3031,6 +3095,29 @@
         client.autofillClientRunOnUiThread(runnable);
     }
 
+    private void setFillDialogTriggerIds(@Nullable List<AutofillId> ids) {
+        mFillDialogTriggerIds = ids;
+    }
+
+    /**
+     * Checks the id of autofill whether supported the fill dialog.
+     *
+     * @hide
+     */
+    public boolean isShowFillDialog(AutofillId id) {
+        if (!hasFillDialogUiFeature() || mFillDialogTriggerIds == null) {
+            return false;
+        }
+        final int size = mFillDialogTriggerIds.size();
+        for (int i = 0; i < size; i++) {
+            AutofillId fillId = mFillDialogTriggerIds.get(i);
+            if (fillId.equalsIgnoreSession(id)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Implementation of the accessibility based compatibility.
      */
@@ -3736,6 +3823,13 @@
                         new FillCallback(callback, id));
             }
         }
+
+        public void notifyFillDialogTriggerIds(List<AutofillId> ids) {
+            final AutofillManager afm = mAfm.get();
+            if (afm != null) {
+                afm.post(() -> afm.setFillDialogTriggerIds(ids));
+            }
+        }
     }
 
     private static final class AugmentedAutofillManagerClient
diff --git a/core/java/android/view/autofill/IAutoFillManagerClient.aidl b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
index 64507aa..2e5967c 100644
--- a/core/java/android/view/autofill/IAutoFillManagerClient.aidl
+++ b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
@@ -148,4 +148,9 @@
      */
     void requestFillFromClient(int id, in InlineSuggestionsRequest request,
             in IFillCallback callback);
+
+    /**
+     * Notifies autofill ids that require to show the fill dialog.
+     */
+    void notifyFillDialogTriggerIds(in List<AutofillId> ids);
 }
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index ff6903e8..08cc31c 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -105,12 +105,14 @@
      *                             current IME.
      * @param configChanges {@link InputMethodInfo#getConfigChanges()} declared by IME.
      * @param stylusHwSupported {@link InputMethodInfo#supportsStylusHandwriting()} declared by IME.
+     * @param shouldShowImeSwitcherWhenImeIsShown {@code true} If the IME switcher is expected to be
+     *                                            shown while the IME is shown.
      * @hide
      */
     @MainThread
     default void initializeInternal(IBinder token,
             IInputMethodPrivilegedOperations privilegedOperations, int configChanges,
-            boolean stylusHwSupported) {
+            boolean stylusHwSupported, boolean shouldShowImeSwitcherWhenImeIsShown) {
         attachToken(token);
     }
 
@@ -229,6 +231,8 @@
      *                        the next {@link #startInput(InputConnection, EditorInfo, IBinder)} as
      *                        long as your implementation of {@link InputMethod} relies on such
      *                        IPCs
+     * @param shouldShowImeSwitcherWhenImeIsShown {@code true} If the IME switcher is expected to be
+     *                                            shown while the IME is shown.
      * @see #startInput(InputConnection, EditorInfo)
      * @see #restartInput(InputConnection, EditorInfo)
      * @see EditorInfo
@@ -237,7 +241,7 @@
     @MainThread
     default void dispatchStartInputWithToken(@Nullable InputConnection inputConnection,
             @NonNull EditorInfo editorInfo, boolean restarting,
-            @NonNull IBinder startInputToken) {
+            @NonNull IBinder startInputToken, boolean shouldShowImeSwitcherWhenImeIsShown) {
         if (restarting) {
             restartInput(inputConnection, editorInfo);
         } else {
@@ -246,6 +250,18 @@
     }
 
     /**
+     * Notifies that whether the IME should show the IME switcher or not is being changed.
+     *
+     * @param shouldShowImeSwitcherWhenImeIsShown {@code true} If the IME switcher is expected to be
+     *                                            shown while the IME is shown.
+     * @hide
+     */
+    @MainThread
+    default void onShouldShowImeSwitcherWhenImeIsShownChanged(
+            boolean shouldShowImeSwitcherWhenImeIsShown) {
+    }
+
+    /**
      * Create a new {@link InputMethodSession} that can be handed to client
      * applications for interacting with the input method.  You can later
      * use {@link #revokeSession(InputMethodSession)} to destroy the session
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index c6f64f4..b00a382 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -301,6 +301,13 @@
     public static final int FLAG_USE_LIGHT_BACKGROUND_LAYOUT = 4;
 
     /**
+     * This mask determines which flags are propagated to nested RemoteViews (either added by
+     * addView, or set as portrait/landscape/sized RemoteViews).
+     */
+    static final int FLAG_MASK_TO_PROPAGATE =
+            FLAG_WIDGET_IS_COLLECTION_CHILD | FLAG_USE_LIGHT_BACKGROUND_LAYOUT;
+
+    /**
      * A ReadWriteHelper which has the same behavior as ReadWriteHelper.DEFAULT, but which is
      * intentionally a different instance in order to trick Bundle reader so that it doesn't allow
      * lazy initialization.
@@ -467,6 +474,18 @@
      */
     public void addFlags(@ApplyFlags int flags) {
         mApplyFlags = mApplyFlags | flags;
+
+        int flagsToPropagate = flags & FLAG_MASK_TO_PROPAGATE;
+        if (flagsToPropagate != 0) {
+            if (hasSizedRemoteViews()) {
+                for (RemoteViews remoteView : mSizedRemoteViews) {
+                    remoteView.addFlags(flagsToPropagate);
+                }
+            } else if (hasLandscapeAndPortraitLayouts()) {
+                mLandscape.addFlags(flagsToPropagate);
+                mPortrait.addFlags(flagsToPropagate);
+            }
+        }
     }
 
     /**
@@ -2407,6 +2426,10 @@
             // will return -1.
             final int nextChild = getNextRecyclableChild(target);
             RemoteViews rvToApply = mNestedViews.getRemoteViewsToApply(context);
+
+            int flagsToPropagate = mApplyFlags & FLAG_MASK_TO_PROPAGATE;
+            if (flagsToPropagate != 0) rvToApply.addFlags(flagsToPropagate);
+
             if (nextChild >= 0 && mStableId != NO_ID) {
                 // At that point, the views starting at index nextChild are the ones recyclable but
                 // not yet recycled. All views added on that round of application are placed before.
@@ -2419,8 +2442,8 @@
                             target.removeViews(nextChild, recycledViewIndex - nextChild);
                         }
                         setNextRecyclableChild(target, nextChild + 1, target.getChildCount());
-                        rvToApply.reapply(context, child, handler, null /* size */, colorResources,
-                                false /* topLevel */);
+                        rvToApply.reapplyNestedViews(context, child, rootParent, handler,
+                                null /* size */, colorResources);
                         return;
                     }
                     // If we cannot recycle the views, we still remove all views in between to
@@ -2431,8 +2454,8 @@
             // If we cannot recycle, insert the new view before the next recyclable child.
 
             // Inflate nested views and add as children
-            View nestedView = rvToApply.apply(context, target, handler, null /* size */,
-                    colorResources);
+            View nestedView = rvToApply.applyNestedViews(context, target, rootParent, handler,
+                    null /* size */, colorResources);
             if (mStableId != NO_ID) {
                 setStableId(nestedView, mStableId);
             }
@@ -3780,7 +3803,7 @@
      * @param parcel
      */
     public RemoteViews(Parcel parcel) {
-        this(parcel, /* rootParent= */ null, /* info= */ null, /* depth= */ 0);
+        this(parcel, /* rootData= */ null, /* info= */ null, /* depth= */ 0);
     }
 
     private RemoteViews(@NonNull Parcel parcel, @Nullable HierarchyRootData rootData,
@@ -5580,6 +5603,16 @@
         return result;
     }
 
+    private View applyNestedViews(Context context, ViewGroup directParent,
+            ViewGroup rootParent, InteractionHandler handler, SizeF size,
+            ColorResources colorResources) {
+        RemoteViews rvToApply = getRemoteViewsToApply(context, size);
+
+        View result = inflateView(context, rvToApply, directParent, 0, colorResources);
+        rvToApply.performApply(result, rootParent, handler, colorResources);
+        return result;
+    }
+
     private View inflateView(Context context, RemoteViews rv, ViewGroup parent) {
         return inflateView(context, rv, parent, 0, null);
     }
@@ -5895,6 +5928,12 @@
         }
     }
 
+    private void reapplyNestedViews(Context context, View v, ViewGroup rootParent,
+            InteractionHandler handler, SizeF size, ColorResources colorResources) {
+        RemoteViews rvToApply = getRemoteViewsToReapply(context, v, size);
+        rvToApply.performApply(v, rootParent, handler, colorResources);
+    }
+
     /**
      * Applies all the actions to the provided view, moving as much of the task on the background
      * thread as possible.
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index fd1e848..3fa62e0 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -599,6 +599,7 @@
         private final Rect mTransitionBounds = new Rect();
         private HardwareBuffer mThumbnail;
         private int mAnimations;
+        private @ColorInt int mBackgroundColor;
 
         private AnimationOptions(int type) {
             mType = type;
@@ -608,6 +609,7 @@
             mType = in.readInt();
             mEnterResId = in.readInt();
             mExitResId = in.readInt();
+            mBackgroundColor = in.readInt();
             mOverrideTaskTransition = in.readBoolean();
             mPackageName = in.readString();
             mTransitionBounds.readFromParcel(in);
@@ -624,11 +626,12 @@
         }
 
         public static AnimationOptions makeCustomAnimOptions(String packageName, int enterResId,
-                int exitResId, boolean overrideTaskTransition) {
+                int exitResId, @ColorInt int backgroundColor, boolean overrideTaskTransition) {
             AnimationOptions options = new AnimationOptions(ANIM_CUSTOM);
             options.mPackageName = packageName;
             options.mEnterResId = enterResId;
             options.mExitResId = exitResId;
+            options.mBackgroundColor = backgroundColor;
             options.mOverrideTaskTransition = overrideTaskTransition;
             return options;
         }
@@ -673,6 +676,10 @@
             return mExitResId;
         }
 
+        public @ColorInt int getBackgroundColor() {
+            return mBackgroundColor;
+        }
+
         public boolean getOverrideTaskTransition() {
             return mOverrideTaskTransition;
         }
@@ -698,6 +705,7 @@
             dest.writeInt(mType);
             dest.writeInt(mEnterResId);
             dest.writeInt(mExitResId);
+            dest.writeInt(mBackgroundColor);
             dest.writeBoolean(mOverrideTaskTransition);
             dest.writeString(mPackageName);
             mTransitionBounds.writeToParcel(dest, flags);
@@ -740,7 +748,7 @@
 
         @Override
         public String toString() {
-            return "{ AnimationOtions type= " + typeToString(mType) + " package=" + mPackageName
+            return "{ AnimationOptions type= " + typeToString(mType) + " package=" + mPackageName
                     + " override=" + mOverrideTaskTransition + " b=" + mTransitionBounds + "}";
         }
     }
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 9648008..629a1b3 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -113,7 +113,7 @@
     void notePhoneOn();
     void notePhoneOff();
     void notePhoneSignalStrength(in SignalStrength signalStrength);
-    void notePhoneDataConnectionState(int dataType, boolean hasData, int serviceType);
+    void notePhoneDataConnectionState(int dataType, boolean hasData, int serviceType, int nrFrequency);
     void notePhoneState(int phoneState);
     void noteWifiOn();
     void noteWifiOff();
@@ -145,6 +145,8 @@
     long getAwakeTimeBattery();
     long getAwakeTimePlugged();
 
+    void noteBluetoothOn(int uid, int reason, String packageName);
+    void noteBluetoothOff(int uid, int reason, String packageName);
     void noteBleScanStarted(in WorkSource ws, boolean isUnoptimized);
     void noteBleScanStopped(in WorkSource ws, boolean isUnoptimized);
     void noteBleScanReset();
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 25ee2d0..4fc977f 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -69,10 +69,14 @@
 import android.os.connectivity.WifiActivityEnergyInfo;
 import android.os.connectivity.WifiBatteryStats;
 import android.provider.Settings;
+import android.telephony.Annotation.NetworkType;
 import android.telephony.CellSignalStrength;
+import android.telephony.CellSignalStrengthLte;
+import android.telephony.CellSignalStrengthNr;
 import android.telephony.DataConnectionRealTimeInfo;
 import android.telephony.ModemActivityInfo;
 import android.telephony.ServiceState;
+import android.telephony.ServiceState.RegState;
 import android.telephony.SignalStrength;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
@@ -935,6 +939,119 @@
     final StopwatchTimer[] mPhoneDataConnectionsTimer =
             new StopwatchTimer[NUM_DATA_CONNECTION_TYPES];
 
+    @RadioAccessTechnology
+    int mActiveRat = RADIO_ACCESS_TECHNOLOGY_OTHER;
+
+    private static class RadioAccessTechnologyBatteryStats {
+        /**
+         * This RAT is currently being used.
+         */
+        private boolean mActive = false;
+        /**
+         * Current active frequency range for this RAT.
+         */
+        @ServiceState.FrequencyRange
+        private int mFrequencyRange = ServiceState.FREQUENCY_RANGE_UNKNOWN;
+        /**
+         * Current signal strength for this RAT.
+         */
+        private int mSignalStrength = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+        /**
+         * Timers for each combination of frequency range and signal strength.
+         */
+        public final StopwatchTimer[][] perStateTimers;
+
+        RadioAccessTechnologyBatteryStats(int freqCount, Clock clock, TimeBase timeBase) {
+            perStateTimers =
+                    new StopwatchTimer[freqCount][CellSignalStrength.NUM_SIGNAL_STRENGTH_BINS];
+            for (int i = 0; i < freqCount; i++) {
+                for (int j = 0; j < CellSignalStrength.NUM_SIGNAL_STRENGTH_BINS; j++) {
+                    perStateTimers[i][j] = new StopwatchTimer(clock, null, -1, null, timeBase);
+                }
+            }
+        }
+
+        /**
+         * Note this RAT is currently being used.
+         */
+        public void noteActive(boolean active, long elapsedRealtimeMs) {
+            if (mActive == active) return;
+            mActive = active;
+            if (mActive) {
+                perStateTimers[mFrequencyRange][mSignalStrength].startRunningLocked(
+                        elapsedRealtimeMs);
+            } else {
+                perStateTimers[mFrequencyRange][mSignalStrength].stopRunningLocked(
+                        elapsedRealtimeMs);
+            }
+        }
+
+        /**
+         * Note current frequency range has changed.
+         */
+        public void noteFrequencyRange(@ServiceState.FrequencyRange int frequencyRange,
+                long elapsedRealtimeMs) {
+            if (mFrequencyRange == frequencyRange) return;
+
+            if (!mActive) {
+                // RAT not in use, note the frequency change and move on.
+                mFrequencyRange = frequencyRange;
+                return;
+            }
+            perStateTimers[mFrequencyRange][mSignalStrength].stopRunningLocked(elapsedRealtimeMs);
+            perStateTimers[frequencyRange][mSignalStrength].startRunningLocked(elapsedRealtimeMs);
+            mFrequencyRange = frequencyRange;
+        }
+
+        /**
+         * Note current signal strength has changed.
+         */
+        public void noteSignalStrength(int signalStrength, long elapsedRealtimeMs) {
+            if (mSignalStrength == signalStrength) return;
+
+            if (!mActive) {
+                // RAT not in use, note the signal strength change and move on.
+                mSignalStrength = signalStrength;
+                return;
+            }
+            perStateTimers[mFrequencyRange][mSignalStrength].stopRunningLocked(elapsedRealtimeMs);
+            perStateTimers[mFrequencyRange][signalStrength].startRunningLocked(elapsedRealtimeMs);
+            mSignalStrength = signalStrength;
+        }
+
+        /**
+         * Reset display timers.
+         */
+        public void reset(long elapsedRealtimeUs) {
+            final int size = perStateTimers.length;
+            for (int i = 0; i < size; i++) {
+                for (int j = 0; j < CellSignalStrength.NUM_SIGNAL_STRENGTH_BINS; j++) {
+                    perStateTimers[i][j].reset(false, elapsedRealtimeUs);
+                }
+            }
+        }
+    }
+
+    /**
+     * Number of frequency ranges, keep in sync with {@link ServiceState.FrequencyRange}
+     */
+    private static final int NR_FREQUENCY_COUNT = 5;
+
+    RadioAccessTechnologyBatteryStats[] mPerRatBatteryStats =
+            new RadioAccessTechnologyBatteryStats[RADIO_ACCESS_TECHNOLOGY_COUNT];
+
+    @GuardedBy("this")
+    private RadioAccessTechnologyBatteryStats getRatBatteryStatsLocked(
+            @RadioAccessTechnology int rat) {
+        RadioAccessTechnologyBatteryStats stats = mPerRatBatteryStats[rat];
+        if (stats == null) {
+            final int freqCount = rat == RADIO_ACCESS_TECHNOLOGY_NR ? NR_FREQUENCY_COUNT : 1;
+            stats = new RadioAccessTechnologyBatteryStats(freqCount, mClock, mOnBatteryTimeBase);
+            mPerRatBatteryStats[rat] = stats;
+        }
+        return stats;
+    }
+
     final LongSamplingCounter[] mNetworkByteActivityCounters =
             new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
 
@@ -5886,6 +6003,10 @@
                     + Integer.toHexString(mHistoryCur.states));
             addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             mMobileRadioPowerState = powerState;
+
+            // Inform current RatBatteryStats that the modem active state might have changed.
+            getRatBatteryStatsLocked(mActiveRat).noteActive(active, elapsedRealtimeMs);
+
             if (active) {
                 mMobileRadioActiveTimer.startRunningLocked(elapsedRealtimeMs);
                 mMobileRadioActivePerAppTimer.startRunningLocked(elapsedRealtimeMs);
@@ -6307,21 +6428,86 @@
     @GuardedBy("this")
     public void notePhoneSignalStrengthLocked(SignalStrength signalStrength,
             long elapsedRealtimeMs, long uptimeMs) {
-        // Bin the strength.
-        int bin = signalStrength.getLevel();
-        updateAllPhoneStateLocked(mPhoneServiceStateRaw, mPhoneSimStateRaw, bin,
+        final int overallSignalStrength = signalStrength.getLevel();
+        final SparseIntArray perRatSignalStrength = new SparseIntArray(
+                BatteryStats.RADIO_ACCESS_TECHNOLOGY_COUNT);
+
+        // Extract signal strength level for each RAT.
+        final List<CellSignalStrength> cellSignalStrengths =
+                signalStrength.getCellSignalStrengths();
+        final int size = cellSignalStrengths.size();
+        for (int i = 0; i < size; i++) {
+            CellSignalStrength cellSignalStrength = cellSignalStrengths.get(i);
+            // Map each CellSignalStrength to a BatteryStats.RadioAccessTechnology
+            final int ratType;
+            final int level;
+            if (cellSignalStrength instanceof CellSignalStrengthNr) {
+                ratType = RADIO_ACCESS_TECHNOLOGY_NR;
+                level = cellSignalStrength.getLevel();
+            } else if (cellSignalStrength instanceof CellSignalStrengthLte) {
+                ratType = RADIO_ACCESS_TECHNOLOGY_LTE;
+                level = cellSignalStrength.getLevel();
+            } else {
+                ratType = RADIO_ACCESS_TECHNOLOGY_OTHER;
+                level = cellSignalStrength.getLevel();
+            }
+
+            // According to SignalStrength#getCellSignalStrengths(), multiple of the same
+            // cellSignalStrength can be present. Just take the highest level one for each RAT.
+            if (perRatSignalStrength.get(ratType, -1) < level) {
+                perRatSignalStrength.put(ratType, level);
+            }
+        }
+
+        notePhoneSignalStrengthLocked(overallSignalStrength, perRatSignalStrength,
+                elapsedRealtimeMs, uptimeMs);
+    }
+
+    /**
+     * Note phone signal strength change, including per RAT signal strength.
+     *
+     * @param signalStrength overall signal strength {@see SignalStrength#getLevel()}
+     * @param perRatSignalStrength signal strength of available RATs
+     */
+    @GuardedBy("this")
+    public void notePhoneSignalStrengthLocked(int signalStrength,
+            SparseIntArray perRatSignalStrength) {
+        notePhoneSignalStrengthLocked(signalStrength, perRatSignalStrength,
+                mClock.elapsedRealtime(), mClock.uptimeMillis());
+    }
+
+    /**
+     * Note phone signal strength change, including per RAT signal strength.
+     *
+     * @param signalStrength overall signal strength {@see SignalStrength#getLevel()}
+     * @param perRatSignalStrength signal strength of available RATs
+     */
+    @GuardedBy("this")
+    public void notePhoneSignalStrengthLocked(int signalStrength,
+            SparseIntArray perRatSignalStrength,
+            long elapsedRealtimeMs, long uptimeMs) {
+        // Note each RAT's signal strength.
+        final int size = perRatSignalStrength.size();
+        for (int i = 0; i < size; i++) {
+            final int rat = perRatSignalStrength.keyAt(i);
+            final int ratSignalStrength = perRatSignalStrength.valueAt(i);
+            getRatBatteryStatsLocked(rat).noteSignalStrength(ratSignalStrength, elapsedRealtimeMs);
+        }
+        updateAllPhoneStateLocked(mPhoneServiceStateRaw, mPhoneSimStateRaw, signalStrength,
                 elapsedRealtimeMs, uptimeMs);
     }
 
     @UnsupportedAppUsage
     @GuardedBy("this")
-    public void notePhoneDataConnectionStateLocked(int dataType, boolean hasData, int serviceType) {
-        notePhoneDataConnectionStateLocked(dataType, hasData, serviceType,
+    public void notePhoneDataConnectionStateLocked(@NetworkType int dataType, boolean hasData,
+            @RegState int serviceType, @ServiceState.FrequencyRange int nrFrequency) {
+        notePhoneDataConnectionStateLocked(dataType, hasData, serviceType, nrFrequency,
                 mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
     @GuardedBy("this")
-    public void notePhoneDataConnectionStateLocked(int dataType, boolean hasData, int serviceType,
+    public void notePhoneDataConnectionStateLocked(@NetworkType int dataType, boolean hasData,
+            @RegState int serviceType, @ServiceState.FrequencyRange int nrFrequency,
             long elapsedRealtimeMs, long uptimeMs) {
         // BatteryStats uses 0 to represent no network type.
         // Telephony does not have a concept of no network type, and uses 0 to represent unknown.
@@ -6344,6 +6530,13 @@
                 }
             }
         }
+
+        final int newRat = mapNetworkTypeToRadioAccessTechnology(bin);
+        if (newRat == RADIO_ACCESS_TECHNOLOGY_NR) {
+            // Note possible frequency change for the NR RAT.
+            getRatBatteryStatsLocked(newRat).noteFrequencyRange(nrFrequency, elapsedRealtimeMs);
+        }
+
         if (DEBUG) Log.i(TAG, "Phone Data Connection -> " + dataType + " = " + hasData);
         if (mPhoneDataConnectionType != bin) {
             mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_DATA_CONNECTION_MASK)
@@ -6357,6 +6550,45 @@
             }
             mPhoneDataConnectionType = bin;
             mPhoneDataConnectionsTimer[bin].startRunningLocked(elapsedRealtimeMs);
+
+            if (mActiveRat != newRat) {
+                getRatBatteryStatsLocked(mActiveRat).noteActive(false, elapsedRealtimeMs);
+                mActiveRat = newRat;
+            }
+            final boolean modemActive = mMobileRadioActiveTimer.isRunningLocked();
+            getRatBatteryStatsLocked(newRat).noteActive(modemActive, elapsedRealtimeMs);
+        }
+    }
+
+    @RadioAccessTechnology
+    private static int mapNetworkTypeToRadioAccessTechnology(@NetworkType int dataType) {
+        switch (dataType) {
+            case TelephonyManager.NETWORK_TYPE_NR:
+                return RADIO_ACCESS_TECHNOLOGY_NR;
+            case TelephonyManager.NETWORK_TYPE_LTE:
+                return RADIO_ACCESS_TECHNOLOGY_LTE;
+            case TelephonyManager.NETWORK_TYPE_UNKNOWN: //fallthrough
+            case TelephonyManager.NETWORK_TYPE_GPRS: //fallthrough
+            case TelephonyManager.NETWORK_TYPE_EDGE: //fallthrough
+            case TelephonyManager.NETWORK_TYPE_UMTS: //fallthrough
+            case TelephonyManager.NETWORK_TYPE_CDMA: //fallthrough
+            case TelephonyManager.NETWORK_TYPE_EVDO_0: //fallthrough
+            case TelephonyManager.NETWORK_TYPE_EVDO_A: //fallthrough
+            case TelephonyManager.NETWORK_TYPE_1xRTT: //fallthrough
+            case TelephonyManager.NETWORK_TYPE_HSDPA: //fallthrough
+            case TelephonyManager.NETWORK_TYPE_HSUPA: //fallthrough
+            case TelephonyManager.NETWORK_TYPE_HSPA: //fallthrough
+            case TelephonyManager.NETWORK_TYPE_IDEN: //fallthrough
+            case TelephonyManager.NETWORK_TYPE_EVDO_B: //fallthrough
+            case TelephonyManager.NETWORK_TYPE_EHRPD: //fallthrough
+            case TelephonyManager.NETWORK_TYPE_HSPAP: //fallthrough
+            case TelephonyManager.NETWORK_TYPE_GSM: //fallthrough
+            case TelephonyManager.NETWORK_TYPE_TD_SCDMA: //fallthrough
+            case TelephonyManager.NETWORK_TYPE_IWLAN: //fallthrough
+                return RADIO_ACCESS_TECHNOLOGY_OTHER;
+            default:
+                Slog.w(TAG, "Unhandled NetworkType (" + dataType + "), mapping to OTHER");
+                return RADIO_ACCESS_TECHNOLOGY_OTHER;
         }
     }
 
@@ -7731,6 +7963,23 @@
         return mPhoneDataConnectionsTimer[dataType];
     }
 
+    @Override public long getActiveRadioDurationMs(@RadioAccessTechnology int rat,
+            @ServiceState.FrequencyRange int frequencyRange, int signalStrength,
+            long elapsedRealtimeMs) {
+        final RadioAccessTechnologyBatteryStats stats = mPerRatBatteryStats[rat];
+        if (stats == null) return 0L;
+
+        final int freqCount = stats.perStateTimers.length;
+        if (frequencyRange < 0 || frequencyRange >= freqCount) return 0L;
+
+        final StopwatchTimer[] strengthTimers = stats.perStateTimers[frequencyRange];
+        final int strengthCount = strengthTimers.length;
+        if (signalStrength < 0 || signalStrength >= strengthCount) return 0L;
+
+        return stats.perStateTimers[frequencyRange][signalStrength].getTotalTimeLocked(
+                elapsedRealtimeMs * 1000, STATS_SINCE_CHARGED) / 1000;
+    }
+
     @UnsupportedAppUsage
     @Override public long getMobileRadioActiveTime(long elapsedRealtimeUs, int which) {
         return mMobileRadioActiveTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
@@ -12553,6 +12802,11 @@
             mNetworkByteActivityCounters[i].reset(false, elapsedRealtimeUs);
             mNetworkPacketActivityCounters[i].reset(false, elapsedRealtimeUs);
         }
+        for (int i = 0; i < RADIO_ACCESS_TECHNOLOGY_COUNT; i++) {
+            final RadioAccessTechnologyBatteryStats stats = mPerRatBatteryStats[i];
+            if (stats == null) continue;
+            stats.reset(elapsedRealtimeUs);
+        }
         mMobileRadioActiveTimer.reset(false, elapsedRealtimeUs);
         mMobileRadioActivePerAppTimer.reset(false, elapsedRealtimeUs);
         mMobileRadioActiveAdjustedTime.reset(false, elapsedRealtimeUs);
diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index be91aac..0a29fc52 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -1159,6 +1159,17 @@
                 : Integer.compare(a.transactionCode, b.transactionCode);
     }
 
+    /** @hide */
+    public static void startForBluetooth(Context context) {
+        new BinderCallsStats.SettingsObserver(
+                    context,
+                    new BinderCallsStats(
+                            new BinderCallsStats.Injector(),
+                              com.android.internal.os.BinderLatencyProto.Dims.BLUETOOTH));
+
+    }
+
+
 
     /**
      * Settings observer for other processes (not system_server).
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index a5cf7ce..23ebc9f 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -20,6 +20,7 @@
 import android.content.ComponentName;
 import android.graphics.drawable.Icon;
 import android.graphics.Rect;
+import android.hardware.biometrics.IBiometricContextListener;
 import android.hardware.biometrics.IBiometricSysuiReceiver;
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.fingerprint.IUdfpsHbmListener;
@@ -166,6 +167,8 @@
     * Used to hide the authentication dialog, e.g. when the application cancels authentication.
     */
     void hideAuthenticationDialog();
+    /* Used to notify the biometric service of events that occur outside of an operation. */
+    void setBiometicContextListener(in IBiometricContextListener listener);
 
     /**
      * Sets an instance of IUdfpsHbmListener for UdfpsController.
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index accb986..f28325e 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -20,6 +20,7 @@
 import android.content.ComponentName;
 import android.graphics.drawable.Icon;
 import android.graphics.Rect;
+import android.hardware.biometrics.IBiometricContextListener;
 import android.hardware.biometrics.IBiometricSysuiReceiver;
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.fingerprint.IUdfpsHbmListener;
@@ -125,6 +126,8 @@
     void onBiometricError(int modality, int error, int vendorCode);
     // Used to hide the authentication dialog, e.g. when the application cancels authentication
     void hideAuthenticationDialog();
+    // Used to notify the biometric service of events that occur outside of an operation.
+    void setBiometicContextListener(in IBiometricContextListener listener);
 
     /**
      * Sets an instance of IUdfpsHbmListener for UdfpsController.
diff --git a/core/java/com/android/internal/util/UserIcons.java b/core/java/com/android/internal/util/UserIcons.java
index bfe4323..17b84ff 100644
--- a/core/java/com/android/internal/util/UserIcons.java
+++ b/core/java/com/android/internal/util/UserIcons.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.util;
 
+import android.annotation.ColorInt;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -72,9 +73,30 @@
             // Return colored icon instead
             colorResId = USER_ICON_COLORS[userId % USER_ICON_COLORS.length];
         }
+        return getDefaultUserIconInColor(resources, resources.getColor(colorResId, null));
+    }
+
+    /**
+     * Returns a default user icon in a particular color.
+     *
+     * @param resources resources object to fetch the user icon
+     * @param color the color used for the icon
+     */
+    public static Drawable getDefaultUserIconInColor(Resources resources, @ColorInt int color) {
         Drawable icon = resources.getDrawable(R.drawable.ic_account_circle, null).mutate();
-        icon.setColorFilter(resources.getColor(colorResId, null), Mode.SRC_IN);
+        icon.setColorFilter(color, Mode.SRC_IN);
         icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
         return icon;
     }
+
+    /**
+     * Returns an array containing colors to be used for default user icons.
+     */
+    public static int[] getUserIconColors(Resources resources) {
+        int[] result = new int[USER_ICON_COLORS.length];
+        for (int i = 0; i < result.length; i++) {
+            result[i] = resources.getColor(USER_ICON_COLORS[i], null);
+        }
+        return result;
+    }
 }
diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl
index da24832..d2bc344 100644
--- a/core/java/com/android/internal/view/IInputMethod.aidl
+++ b/core/java/com/android/internal/view/IInputMethod.aidl
@@ -37,7 +37,8 @@
  */
 oneway interface IInputMethod {
     void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps,
-             int configChanges, boolean stylusHwSupported);
+             int configChanges, boolean stylusHwSupported,
+             boolean shouldShowImeSwitcherWhenImeIsShown);
 
     void onCreateInlineSuggestionsRequest(in InlineSuggestionsRequestInfo requestInfo,
             in IInlineSuggestionsRequestCallback cb);
@@ -47,7 +48,10 @@
     void unbindInput();
 
     void startInput(in IBinder startInputToken, in IInputContext inputContext,
-            in EditorInfo attribute, boolean restarting);
+            in EditorInfo attribute, boolean restarting,
+             boolean shouldShowImeSwitcherWhenImeIsShown);
+
+    void onShouldShowImeSwitcherWhenImeIsShownChanged(boolean shouldShowImeSwitcherWhenImeIsShown);
 
     void createSession(in InputChannel channel, IInputSessionCallback callback);
 
diff --git a/core/jni/android/opengl/OWNERS b/core/jni/android/opengl/OWNERS
new file mode 100644
index 0000000..ce4b907
--- /dev/null
+++ b/core/jni/android/opengl/OWNERS
@@ -0,0 +1 @@
+file:/graphics/java/android/graphics/OWNERS
diff --git a/core/jni/android_hardware_HardwareBuffer.cpp b/core/jni/android_hardware_HardwareBuffer.cpp
index b25b4f7..f462523 100644
--- a/core/jni/android_hardware_HardwareBuffer.cpp
+++ b/core/jni/android_hardware_HardwareBuffer.cpp
@@ -85,7 +85,7 @@
     sp<GraphicBuffer> buffer = new GraphicBuffer(width, height, pixelFormat, layers,
             grallocUsage, std::string("HardwareBuffer pid [") + std::to_string(getpid()) +"]");
     status_t error = buffer->initCheck();
-    if (error < 0) {
+    if (error != OK) {
         if (kDebugGraphicBuffer) {
             ALOGW("createGraphicBuffer() failed in HardwareBuffer.create()");
         }
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index d039bcf..78b403c 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -133,6 +133,18 @@
             MakeGlobalRefOrDie(_env, _env->CallObjectMethod(empty.get(), stringOffsets.intern));
 }
 
+uint64_t htonll(uint64_t ll) {
+    constexpr uint32_t kBytesToTest = 0x12345678;
+    constexpr uint8_t kFirstByte = (const uint8_t &)kBytesToTest;
+    constexpr bool kIsLittleEndian = kFirstByte == 0x78;
+
+    if constexpr (kIsLittleEndian) {
+        return static_cast<uint64_t>(htonl(ll & 0xffffffff)) << 32 | htonl(ll >> 32);
+    } else {
+        return ll;
+    }
+}
+
 static jstring getJavaInternedString(JNIEnv *env, const String8 &string) {
     if (string == "") {
         return gStringOffsets.emptyString;
@@ -193,7 +205,8 @@
         int32_t id = nativeSensor.getId();
         env->CallVoidMethod(sensor, sensorOffsets.setId, id);
         Sensor::uuid_t uuid = nativeSensor.getUuid();
-        env->CallVoidMethod(sensor, sensorOffsets.setUuid, id, uuid.i64[0], uuid.i64[1]);
+        env->CallVoidMethod(sensor, sensorOffsets.setUuid, htonll(uuid.i64[0]),
+                            htonll(uuid.i64[1]));
     }
     return sensor;
 }
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index e13b788..6b82ba8 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -1288,7 +1288,7 @@
                            audioFormatFromNative(nAudioProfile->format), jSamplingRates.get(),
                            jChannelMasks.get(), jChannelIndexMasks.get(), encapsulationType);
 
-    if (jAudioProfile == nullptr) {
+    if (*jAudioProfile == nullptr) {
         return AUDIO_JAVA_ERROR;
     }
 
diff --git a/core/jni/android_server_NetworkManagementSocketTagger.cpp b/core/jni/android_server_NetworkManagementSocketTagger.cpp
index 1be1873..9734ab9 100644
--- a/core/jni/android_server_NetworkManagementSocketTagger.cpp
+++ b/core/jni/android_server_NetworkManagementSocketTagger.cpp
@@ -61,27 +61,9 @@
   return (jint)res;
 }
 
-static jint setCounterSet(JNIEnv* env, jclass, jint setNum, jint uid) {
-  int res = qtaguid_setCounterSet(setNum, uid);
-  if (res < 0) {
-    return (jint)-errno;
-  }
-  return (jint)res;
-}
-
-static jint deleteTagData(JNIEnv* env, jclass, jint tagNum, jint uid) {
-  int res = qtaguid_deleteTagData(tagNum, uid);
-  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},
-  { "native_setCounterSet", "(II)I", (void*)setCounterSet},
-  { "native_deleteTagData", "(II)I", (void*)deleteTagData},
 };
 
 int register_android_server_NetworkManagementSocketTagger(JNIEnv* env) {
diff --git a/core/proto/android/server/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto
index acb7429..d48ea3b 100644
--- a/core/proto/android/server/powermanagerservice.proto
+++ b/core/proto/android/server/powermanagerservice.proto
@@ -127,7 +127,7 @@
     optional bool is_light_device_idle_mode = 25;
     // True if we are currently in device idle mode.
     optional bool is_device_idle_mode = 26;
-    // Set of app ids that we will always respect the wake locks for.
+    // Set of app ids that we will respect the wake locks for while in device idle mode.
     repeated int32 device_idle_whitelist = 27;
     // Set of app ids that are temporarily allowed to acquire wakelocks due to
     // high-pri message
@@ -187,6 +187,8 @@
     // Whether or not the current enhanced discharge prediction is personalized based on device
     // usage or not.
     optional bool is_enhanced_discharge_prediction_personalized = 54;
+    optional bool is_low_power_standby_active = 55;
+    optional LowPowerStandbyControllerDumpProto low_power_standby_controller = 56;
 }
 
 // A com.android.server.power.PowerManagerService.SuspendBlockerImpl object.
@@ -209,6 +211,8 @@
         // When this wake lock is released, poke the user activity timer
         // so the screen stays on for a little longer.
         optional bool is_on_after_release = 2;
+        // The wakelock is held by the system server on request by another app.
+        optional bool system_wakelock = 3;
     }
 
     optional .android.os.WakeLockLevelEnum lock_level = 1;
@@ -428,3 +432,39 @@
 
     // Next tag: 23
 }
+
+message LowPowerStandbyControllerDumpProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    // True if Low Power Standby is active
+    optional bool is_active = 1;
+
+    // True if Low Power Standby is enabled
+    optional bool is_enabled = 2;
+
+    // True if Low Power Standby is supported
+    optional bool is_supported_config = 3;
+
+    // True if Low Power Standby is enabled by default
+    optional bool is_enabled_by_default_config = 4;
+
+    // True if the device is currently interactive
+    optional bool is_interactive = 5;
+
+    // Time (in elapsedRealtime) when the device was last interactive
+    optional bool last_interactive_time = 6;
+
+    // Time (in milliseconds) after becoming non-interactive that Low Power Standby can activate
+    optional int32 standby_timeout_config = 7;
+
+    // True if the device has entered idle mode since becoming non-interactive
+    optional int32 idle_since_non_interactive = 8;
+
+    // True if the device is currently in idle mode
+    optional int32 is_device_idle = 9;
+
+    // Set of app ids that are exempt form low power standby
+    repeated int32 allowlist = 10;
+
+    // Next tag: 11
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index d8592e5..6e54197 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -107,6 +107,7 @@
     <protected-broadcast android:name="android.os.action.POWER_SAVE_WHITELIST_CHANGED" />
     <protected-broadcast android:name="android.os.action.POWER_SAVE_TEMP_WHITELIST_CHANGED" />
     <protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGED_INTERNAL" />
+    <protected-broadcast android:name="android.os.action.LOW_POWER_STANDBY_ENABLED_CHANGED" />
     <protected-broadcast android:name="android.os.action.ENHANCED_DISCHARGE_PREDICTION_CHANGED" />
 
     <!-- @deprecated This is rarely used and will be phased out soon. -->
@@ -718,6 +719,7 @@
     <protected-broadcast android:name="android.safetycenter.action.REFRESH_SAFETY_SOURCES" />
     <protected-broadcast android:name="android.app.action.DEVICE_POLICY_RESOURCE_UPDATED" />
     <protected-broadcast android:name="android.intent.action.SHOW_FOREGROUND_SERVICE_MANAGER" />
+    <protected-broadcast android:name="android.service.autofill.action.DELAYED_FILL" />
 
     <!-- ====================================================================== -->
     <!--                          RUNTIME PERMISSIONS                           -->
@@ -3671,6 +3673,13 @@
     <permission android:name="android.permission.SIGNAL_PERSISTENT_PROCESSES"
         android:protectionLevel="signature|privileged|development" />
 
+    <!-- @hide @SystemApi Must be required by a
+         {@link com.android.service.tracing.TraceReportService}, to ensure that only the system
+         can bind to it.
+        <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.BIND_TRACE_REPORT_SERVICE"
+                android:protectionLevel="signature" />
+
     <!-- @hide @SystemApi @TestApi
          Allow an application to approve incident and bug reports to be
          shared off-device.  There can be only one application installed on the
@@ -4965,6 +4974,11 @@
     <permission android:name="android.permission.USER_ACTIVITY"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @hide @SystemApi Allows an application to manage Low Power Standby settings.
+         <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.MANAGE_LOW_POWER_STANDBY"
+                android:protectionLevel="signature|privileged" />
+
    <!-- @hide Allows low-level access to tun tap driver -->
     <permission android:name="android.permission.NET_TUNNELING"
         android:protectionLevel="signature" />
@@ -5412,7 +5426,7 @@
                 android:protectionLevel="signature|setup|role" />
 
     <!-- Allows access to keyguard secure storage.  Only allowed for system processes.
-        @hide -->
+         @hide @TestApi -->
     <permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE"
         android:protectionLevel="signature|setup" />
 
diff --git a/core/res/res/layout/autofill_fill_dialog.xml b/core/res/res/layout/autofill_fill_dialog.xml
new file mode 100644
index 0000000..cce9593
--- /dev/null
+++ b/core/res/res/layout/autofill_fill_dialog.xml
@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- NOTE: outer layout is required to provide proper shadow. -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/autofill_dialog_picker"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:layout_marginTop="@dimen/autofill_save_outer_top_margin"
+    android:padding="@dimen/autofill_save_outer_top_padding"
+    android:elevation="@dimen/autofill_elevation"
+    android:background="?android:attr/colorBackground"
+    android:orientation="vertical">
+
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center_horizontal"
+        android:paddingStart="@dimen/autofill_save_inner_padding"
+        android:paddingEnd="@dimen/autofill_save_inner_padding"
+        android:orientation="vertical">
+
+        <ImageView
+            android:id="@+id/autofill_service_icon"
+            android:scaleType="fitStart"
+            android:visibility="gone"
+            android:layout_width="@dimen/autofill_dialog_icon_size"
+            android:layout_height="@dimen/autofill_dialog_icon_size"/>
+
+        <LinearLayout
+            android:id="@+id/autofill_dialog_header"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_horizontal"
+            android:paddingStart="@dimen/autofill_save_inner_padding"
+            android:paddingEnd="@dimen/autofill_save_inner_padding"
+            android:visibility="gone"
+            android:foreground="?attr/listChoiceBackgroundIndicator"
+            style="@style/AutofillDatasetPicker" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/autofill_dialog_container"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center_horizontal"
+        android:paddingStart="@dimen/autofill_save_inner_padding"
+        android:paddingEnd="@dimen/autofill_save_inner_padding"
+        android:visibility="gone"
+        android:foreground="?attr/listChoiceBackgroundIndicator"
+        style="@style/AutofillDatasetPicker" />
+
+    <ListView
+        android:id="@+id/autofill_dialog_list"
+        android:layout_weight="1"
+        android:layout_width="fill_parent"
+        android:layout_height="0dp"
+        android:drawSelectorOnTop="true"
+        android:clickable="true"
+        android:divider="@null"
+        android:visibility="gone"
+        android:paddingStart="@dimen/autofill_save_inner_padding"
+        android:paddingEnd="@dimen/autofill_save_inner_padding"
+        android:foreground="?attr/listChoiceBackgroundIndicator"
+        style="@style/AutofillDatasetPicker" />
+
+    <com.android.internal.widget.ButtonBarLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="end"
+        android:padding="@dimen/autofill_save_button_bar_padding"
+        android:clipToPadding="false"
+        android:orientation="horizontal">
+
+        <Button
+            android:id="@+id/autofill_dialog_no"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            style="?android:attr/buttonBarButtonStyle"
+            android:text="@string/dismiss_action">
+        </Button>
+
+        <Space
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_weight="1"
+            android:visibility="invisible">
+        </Space>
+
+        <Button
+            android:id="@+id/autofill_dialog_yes"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            style="@style/Widget.DeviceDefault.Button.Colored"
+            android:text="@string/autofill_save_yes"
+            android:visibility="gone" >
+        </Button>
+
+    </com.android.internal.widget.ButtonBarLayout>
+
+</LinearLayout>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 74cd519..0edc0d6 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2707,6 +2707,9 @@
          Values are bandwidth_estimator, carrier_config and modem. -->
     <string name="config_bandwidthEstimateSource">bandwidth_estimator</string>
 
+    <!-- Whether force to enable telephony new data stack or not -->
+    <bool name="config_force_enable_telephony_new_data_stack">false</bool>
+
     <!-- Whether WiFi display is supported by this device.
          There are many prerequisites for this feature to work correctly.
          Here are a few of them:
@@ -5624,4 +5627,14 @@
 
     <!-- Whether or not to enable the lock screen entry point for the QR code scanner. -->
     <bool name="config_enableQrCodeScannerOnLockScreen">false</bool>
+
+    <!-- Whether Low Power Standby is supported and can be enabled. -->
+    <bool name="config_lowPowerStandbySupported">false</bool>
+
+    <!-- If supported, whether Low Power Standby is enabled by default. -->
+    <bool name="config_lowPowerStandbyEnabledByDefault">false</bool>
+
+    <!-- The amount of time after becoming non-interactive (in ms) after which
+         Low Power Standby can activate. -->
+    <integer name="config_lowPowerStandbyNonInteractiveTimeout">5000</integer>
 </resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 771c072..4874e65 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -877,6 +877,9 @@
     <!-- Maximum number of datasets that are visible in the UX picker without scrolling -->
     <integer name="autofill_max_visible_datasets">3</integer>
 
+    <!-- Size of an icon in the Autolfill fill dialog -->
+    <dimen name="autofill_dialog_icon_size">56dp</dimen>
+
     <!-- Size of a slice shortcut view -->
     <dimen name="slice_shortcut_size">56dp</dimen>
     <!-- Size of action icons in a slice -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 8d51dbe..602d5f9 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -472,6 +472,7 @@
   <java-symbol type="integer" name="config_mobile_mtu" />
   <java-symbol type="array"   name="config_mobile_tcp_buffers" />
   <java-symbol type="string"  name="config_tcp_buffers" />
+  <java-symbol type="bool" name="config_force_enable_telephony_new_data_stack" />
   <java-symbol type="integer" name="config_volte_replacement_rat"/>
   <java-symbol type="integer" name="config_valid_wappush_index" />
   <java-symbol type="integer" name="config_overrideHasPermanentMenuKey" />
@@ -3510,6 +3511,7 @@
   <java-symbol type="layout" name="autofill_dataset_picker"/>
   <java-symbol type="layout" name="autofill_dataset_picker_fullscreen"/>
   <java-symbol type="layout" name="autofill_dataset_picker_header_footer"/>
+  <java-symbol type="layout" name="autofill_fill_dialog"/>
   <java-symbol type="id" name="autofill" />
   <java-symbol type="id" name="autofill_dataset_footer"/>
   <java-symbol type="id" name="autofill_dataset_header"/>
@@ -3522,6 +3524,13 @@
   <java-symbol type="id" name="autofill_save_no" />
   <java-symbol type="id" name="autofill_save_title" />
   <java-symbol type="id" name="autofill_save_yes" />
+  <java-symbol type="id" name="autofill_service_icon" />
+  <java-symbol type="id" name="autofill_dialog_picker"/>
+  <java-symbol type="id" name="autofill_dialog_header"/>
+  <java-symbol type="id" name="autofill_dialog_container"/>
+  <java-symbol type="id" name="autofill_dialog_list"/>
+  <java-symbol type="id" name="autofill_dialog_no" />
+  <java-symbol type="id" name="autofill_dialog_yes" />
   <java-symbol type="string" name="autofill_error_cannot_autofill" />
   <java-symbol type="string" name="autofill_picker_no_suggestions" />
   <java-symbol type="string" name="autofill_picker_some_suggestions" />
@@ -4672,4 +4681,8 @@
   <java-symbol type="string" name="notification_content_abusive_bg_apps"/>
   <java-symbol type="string" name="notification_content_long_running_fgs"/>
   <java-symbol type="string" name="notification_action_check_bg_apps"/>
+
+  <java-symbol type="bool" name="config_lowPowerStandbySupported" />
+  <java-symbol type="bool" name="config_lowPowerStandbyEnabledByDefault" />
+  <java-symbol type="integer" name="config_lowPowerStandbyNonInteractiveTimeout" />
 </resources>
diff --git a/core/tests/coretests/res/layout/remote_view_relative_layout.xml b/core/tests/coretests/res/layout/remote_view_relative_layout.xml
new file mode 100644
index 0000000..713a4c8
--- /dev/null
+++ b/core/tests/coretests/res/layout/remote_view_relative_layout.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2008, 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.
+*/
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" />
diff --git a/core/tests/coretests/res/layout/remote_view_relative_layout_with_theme.xml b/core/tests/coretests/res/layout/remote_view_relative_layout_with_theme.xml
new file mode 100644
index 0000000..74c939b
--- /dev/null
+++ b/core/tests/coretests/res/layout/remote_view_relative_layout_with_theme.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2008, 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.
+*/
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/themed_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:theme="@style/RelativeLayoutAlignTop25Alpha"/>
diff --git a/core/tests/coretests/res/layout/remote_views_light_background_text.xml b/core/tests/coretests/res/layout/remote_views_light_background_text.xml
new file mode 100644
index 0000000..f300f09
--- /dev/null
+++ b/core/tests/coretests/res/layout/remote_views_light_background_text.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2016 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/light_background_text"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
diff --git a/core/tests/coretests/res/layout/remote_views_list.xml b/core/tests/coretests/res/layout/remote_views_list.xml
new file mode 100644
index 0000000..ca43bc8
--- /dev/null
+++ b/core/tests/coretests/res/layout/remote_views_list.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2016 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<ListView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/list"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"/>
+
diff --git a/core/tests/coretests/res/values/styles.xml b/core/tests/coretests/res/values/styles.xml
index 352b4dc..32eebb35 100644
--- a/core/tests/coretests/res/values/styles.xml
+++ b/core/tests/coretests/res/values/styles.xml
@@ -34,6 +34,16 @@
     <style name="LayoutInDisplayCutoutModeAlways">
         <item name="android:windowLayoutInDisplayCutoutMode">always</item>
     </style>
+    <style name="RelativeLayoutAlignBottom50Alpha">
+        <item name="android:layout_alignParentTop">false</item>
+        <item name="android:layout_alignParentBottom">true</item>
+        <item name="android:alpha">0.5</item>
+    </style>
+    <style name="RelativeLayoutAlignTop25Alpha">
+        <item name="android:layout_alignParentTop">true</item>
+        <item name="android:layout_alignParentBottom">false</item>
+        <item name="android:alpha">0.25</item>
+    </style>
     <style name="WindowBackgroundColorLiteral">
         <item name="android:windowBackground">#00FF00</item>
     </style>
diff --git a/core/tests/coretests/src/android/view/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
similarity index 96%
rename from core/tests/coretests/src/android/view/HandwritingInitiatorTest.java
rename to core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
index 632a1a9..e11fe17 100644
--- a/core/tests/coretests/src/android/view/HandwritingInitiatorTest.java
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.view;
+package android.view.stylus;
 
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_MOVE;
@@ -33,6 +33,12 @@
 import android.content.Context;
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
+import android.view.HandwritingInitiator;
+import android.view.InputDevice;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
 import android.view.inputmethod.InputMethodManager;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -47,7 +53,7 @@
  * Tests for {@link HandwritingInitiator}
  *
  * Build/Install/Run:
- *  atest FrameworksCoreTests:HandwritingInitiatorTest
+ *  atest FrameworksCoreTests:android.view.stylus.HandwritingInitiatorTest
  */
 @Presubmit
 @SmallTest
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
index 059c764..00b3693 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
@@ -16,8 +16,12 @@
 
 package android.widget;
 
+import static com.android.internal.R.id.pending_intent_tag;
+
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 
@@ -31,7 +35,10 @@
 import android.graphics.drawable.Drawable;
 import android.os.AsyncTask;
 import android.os.Binder;
+import android.os.Looper;
 import android.os.Parcel;
+import android.util.SizeF;
+import android.view.ContextThemeWrapper;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -49,6 +56,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Map;
 import java.util.concurrent.CountDownLatch;
 
 /**
@@ -261,6 +269,148 @@
         verifyViewTree(syncView, asyncView, "row1-c1", "row1-c2", "row1-c3", "row2-c1", "row2-c2");
     }
 
+    @Test
+    public void nestedViews_setRemoteAdapter_intent() {
+        Looper.prepare();
+
+        AppWidgetHostView widget = new AppWidgetHostView(mContext);
+        RemoteViews top = new RemoteViews(mPackage, R.layout.remote_view_host);
+        RemoteViews inner1 = new RemoteViews(mPackage, R.layout.remote_view_host);
+        RemoteViews inner2 = new RemoteViews(mPackage, R.layout.remote_views_list);
+        inner2.setRemoteAdapter(R.id.list, new Intent());
+        inner1.addView(R.id.container, inner2);
+        top.addView(R.id.container, inner1);
+
+        View view = top.apply(mContext, widget);
+        widget.addView(view);
+
+        ListView listView = (ListView) view.findViewById(R.id.list);
+        listView.onRemoteAdapterConnected();
+        assertNotNull(listView.getAdapter());
+
+        top.reapply(mContext, view);
+        listView = (ListView) view.findViewById(R.id.list);
+        assertNotNull(listView.getAdapter());
+    }
+
+    @Test
+    public void nestedViews_setRemoteAdapter_remoteCollectionItems() {
+        AppWidgetHostView widget = new AppWidgetHostView(mContext);
+        RemoteViews top = new RemoteViews(mPackage, R.layout.remote_view_host);
+        RemoteViews inner1 = new RemoteViews(mPackage, R.layout.remote_view_host);
+        RemoteViews inner2 = new RemoteViews(mPackage, R.layout.remote_views_list);
+        inner2.setRemoteAdapter(
+                R.id.list,
+                new RemoteViews.RemoteCollectionItems.Builder()
+                    .addItem(0, new RemoteViews(mPackage, R.layout.remote_view_host))
+                    .build());
+        inner1.addView(R.id.container, inner2);
+        top.addView(R.id.container, inner1);
+
+        View view = top.apply(mContext, widget);
+        widget.addView(view);
+
+        ListView listView = (ListView) view.findViewById(R.id.list);
+        assertNotNull(listView.getAdapter());
+
+        top.reapply(mContext, view);
+        listView = (ListView) view.findViewById(R.id.list);
+        assertNotNull(listView.getAdapter());
+    }
+
+    @Test
+    public void nestedViews_collectionChildFlag() throws Exception {
+        RemoteViews nested = new RemoteViews(mPackage, R.layout.remote_views_text);
+        nested.setOnClickPendingIntent(
+                R.id.text,
+                PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE)
+        );
+
+        RemoteViews listItem = new RemoteViews(mPackage, R.layout.remote_view_host);
+        listItem.addView(R.id.container, nested);
+        listItem.addFlags(RemoteViews.FLAG_WIDGET_IS_COLLECTION_CHILD);
+
+        View view = listItem.apply(mContext, mContainer);
+        TextView text = (TextView) view.findViewById(R.id.text);
+        assertNull(text.getTag(pending_intent_tag));
+    }
+
+    @Test
+    public void landscapePortraitViews_collectionChildFlag() throws Exception {
+        RemoteViews inner = new RemoteViews(mPackage, R.layout.remote_views_text);
+        inner.setOnClickPendingIntent(
+                R.id.text,
+                PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE)
+        );
+
+        RemoteViews listItem = new RemoteViews(inner, inner);
+        listItem.addFlags(RemoteViews.FLAG_WIDGET_IS_COLLECTION_CHILD);
+
+        View view = listItem.apply(mContext, mContainer);
+        TextView text = (TextView) view.findViewById(R.id.text);
+        assertNull(text.getTag(pending_intent_tag));
+    }
+
+    @Test
+    public void sizedViews_collectionChildFlag() throws Exception {
+        RemoteViews inner = new RemoteViews(mPackage, R.layout.remote_views_text);
+        inner.setOnClickPendingIntent(
+                R.id.text,
+                PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE)
+        );
+
+        RemoteViews listItem = new RemoteViews(
+                Map.of(new SizeF(0, 0), inner, new SizeF(100, 100), inner));
+        listItem.addFlags(RemoteViews.FLAG_WIDGET_IS_COLLECTION_CHILD);
+
+        View view = listItem.apply(mContext, mContainer);
+        TextView text = (TextView) view.findViewById(R.id.text);
+        assertNull(text.getTag(pending_intent_tag));
+    }
+
+    @Test
+    public void nestedViews_lightBackgroundLayoutFlag() {
+        RemoteViews nested = new RemoteViews(mPackage, R.layout.remote_views_text);
+        nested.setLightBackgroundLayoutId(R.layout.remote_views_light_background_text);
+
+        RemoteViews parent = new RemoteViews(mPackage, R.layout.remote_view_host);
+        parent.addView(R.id.container, nested);
+        parent.setLightBackgroundLayoutId(R.layout.remote_view_host);
+        parent.addFlags(RemoteViews.FLAG_USE_LIGHT_BACKGROUND_LAYOUT);
+
+        View view = parent.apply(mContext, mContainer);
+        assertNull(view.findViewById(R.id.text));
+        assertNotNull(view.findViewById(R.id.light_background_text));
+    }
+
+
+    @Test
+    public void landscapePortraitViews_lightBackgroundLayoutFlag() {
+        RemoteViews inner = new RemoteViews(mPackage, R.layout.remote_views_text);
+        inner.setLightBackgroundLayoutId(R.layout.remote_views_light_background_text);
+
+        RemoteViews parent = new RemoteViews(inner, inner);
+        parent.addFlags(RemoteViews.FLAG_USE_LIGHT_BACKGROUND_LAYOUT);
+
+        View view = parent.apply(mContext, mContainer);
+        assertNull(view.findViewById(R.id.text));
+        assertNotNull(view.findViewById(R.id.light_background_text));
+    }
+
+    @Test
+    public void sizedViews_lightBackgroundLayoutFlag() {
+        RemoteViews inner = new RemoteViews(mPackage, R.layout.remote_views_text);
+        inner.setLightBackgroundLayoutId(R.layout.remote_views_light_background_text);
+
+        RemoteViews parent = new RemoteViews(
+                Map.of(new SizeF(0, 0), inner, new SizeF(100, 100), inner));
+        parent.addFlags(RemoteViews.FLAG_USE_LIGHT_BACKGROUND_LAYOUT);
+
+        View view = parent.apply(mContext, mContainer);
+        assertNull(view.findViewById(R.id.text));
+        assertNotNull(view.findViewById(R.id.light_background_text));
+    }
+
     private RemoteViews createViewChained(int depth, String... texts) {
         RemoteViews result = new RemoteViews(mPackage, R.layout.remote_view_host);
 
@@ -483,6 +633,47 @@
                 index, inflated.getTag(com.android.internal.R.id.notification_action_index_tag));
     }
 
+    @Test
+    public void nestedViews_themesPropagateCorrectly() {
+        Context themedContext =
+                new ContextThemeWrapper(mContext, R.style.RelativeLayoutAlignBottom50Alpha);
+        RelativeLayout rootParent = new RelativeLayout(themedContext);
+
+        RemoteViews top = new RemoteViews(mPackage, R.layout.remote_view_relative_layout);
+        RemoteViews inner1 =
+                new RemoteViews(mPackage, R.layout.remote_view_relative_layout_with_theme);
+        RemoteViews inner2 =
+                new RemoteViews(mPackage, R.layout.remote_view_relative_layout);
+
+        inner1.addView(R.id.themed_layout, inner2);
+        top.addView(R.id.container, inner1);
+
+        RelativeLayout root = (RelativeLayout) top.apply(themedContext, rootParent);
+        assertEquals(0.5, root.getAlpha(), 0.);
+        RelativeLayout.LayoutParams rootParams =
+                (RelativeLayout.LayoutParams) root.getLayoutParams();
+        assertEquals(RelativeLayout.TRUE,
+                rootParams.getRule(RelativeLayout.ALIGN_PARENT_BOTTOM));
+
+        // The theme is set on inner1View and its descendants. However, inner1View does
+        // not get its layout params from its theme (though its descendants do), but other
+        // attributes such as alpha are set.
+        RelativeLayout inner1View = (RelativeLayout) root.getChildAt(0);
+        assertEquals(R.id.themed_layout, inner1View.getId());
+        assertEquals(0.25, inner1View.getAlpha(), 0.);
+        RelativeLayout.LayoutParams inner1Params =
+                (RelativeLayout.LayoutParams) inner1View.getLayoutParams();
+        assertEquals(RelativeLayout.TRUE,
+                inner1Params.getRule(RelativeLayout.ALIGN_PARENT_BOTTOM));
+
+        RelativeLayout inner2View = (RelativeLayout) inner1View.getChildAt(0);
+        assertEquals(0.25, inner2View.getAlpha(), 0.);
+        RelativeLayout.LayoutParams inner2Params =
+                (RelativeLayout.LayoutParams) inner2View.getLayoutParams();
+        assertEquals(RelativeLayout.TRUE,
+                inner2Params.getRule(RelativeLayout.ALIGN_PARENT_TOP));
+    }
+
     private class WidgetContainer extends AppWidgetHostView {
         int[] mSharedViewIds;
         String[] mSharedViewNames;
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
index b655369..f5cbffb 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -17,6 +17,7 @@
 package com.android.internal.os;
 
 import static android.os.BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS;
+import static android.os.BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR;
 import static android.os.BatteryStats.STATS_SINCE_CHARGED;
 import static android.os.BatteryStats.WAKE_TYPE_PARTIAL;
 
@@ -30,6 +31,12 @@
 import android.os.Process;
 import android.os.UserHandle;
 import android.os.WorkSource;
+import android.telephony.Annotation;
+import android.telephony.CellSignalStrength;
+import android.telephony.DataConnectionRealTimeInfo;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.util.SparseIntArray;
 import android.util.SparseLongArray;
 import android.view.Display;
 
@@ -1165,6 +1172,185 @@
                 "D", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi);
     }
 
+    @SmallTest
+    public void testGetPerStateActiveRadioDurationMs() {
+        final MockClock clock = new MockClock(); // holds realtime and uptime in ms
+        final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clock);
+        final int ratCount = BatteryStats.RADIO_ACCESS_TECHNOLOGY_COUNT;
+        final int frequencyCount = ServiceState.FREQUENCY_RANGE_MMWAVE + 1;
+        final int txLevelCount = CellSignalStrength.getNumSignalStrengthLevels();
+
+        final long[][][] expectedDurationsMs = new long[ratCount][frequencyCount][txLevelCount];
+        for (int rat = 0; rat < ratCount; rat++) {
+            for (int freq = 0; freq < frequencyCount; freq++) {
+                for (int txLvl = 0; txLvl < txLevelCount; txLvl++) {
+                    expectedDurationsMs[rat][freq][txLvl] = 0;
+                }
+            }
+        }
+
+        class ModemAndBatteryState {
+            public long currentTimeMs = 100;
+            public boolean onBattery = false;
+            public boolean modemActive = false;
+            @Annotation.NetworkType
+            public int currentNetworkDataType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+            @BatteryStats.RadioAccessTechnology
+            public int currentRat = BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER;
+            @ServiceState.FrequencyRange
+            public int currentFrequencyRange = ServiceState.FREQUENCY_RANGE_UNKNOWN;
+            public SparseIntArray currentSignalStrengths = new SparseIntArray();
+
+            void setOnBattery(boolean onBattery) {
+                this.onBattery = onBattery;
+                bi.updateTimeBasesLocked(onBattery, Display.STATE_OFF, currentTimeMs * 1000,
+                        currentTimeMs * 1000);
+            }
+
+            void setModemActive(boolean active) {
+                modemActive = active;
+                final int state = active ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH
+                        : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
+                bi.noteMobileRadioPowerStateLocked(state, currentTimeMs * 1000_000L, UID);
+            }
+
+            void setRatType(@Annotation.NetworkType int dataType,
+                    @BatteryStats.RadioAccessTechnology int rat) {
+                currentNetworkDataType = dataType;
+                currentRat = rat;
+                bi.notePhoneDataConnectionStateLocked(dataType, true, ServiceState.STATE_IN_SERVICE,
+                        currentFrequencyRange);
+            }
+
+            void setFrequencyRange(@ServiceState.FrequencyRange int frequency) {
+                currentFrequencyRange = frequency;
+                bi.notePhoneDataConnectionStateLocked(currentNetworkDataType, true,
+                        ServiceState.STATE_IN_SERVICE, frequency);
+            }
+
+            void setSignalStrength(@BatteryStats.RadioAccessTechnology int rat, int strength) {
+                currentSignalStrengths.put(rat, strength);
+                final int size = currentSignalStrengths.size();
+                final int newestGenSignalStrength = currentSignalStrengths.valueAt(size - 1);
+                bi.notePhoneSignalStrengthLocked(newestGenSignalStrength, currentSignalStrengths);
+            }
+        }
+        final ModemAndBatteryState state = new ModemAndBatteryState();
+
+        IntConsumer incrementTime = inc -> {
+            state.currentTimeMs += inc;
+            clock.realtime = clock.uptime = state.currentTimeMs;
+
+            // If the device is not on battery, no timers should increment.
+            if (!state.onBattery) return;
+            // If the modem is not active, no timers should increment.
+            if (!state.modemActive) return;
+
+            final int currentRat = state.currentRat;
+            final int currentFrequencyRange =
+                    currentRat == RADIO_ACCESS_TECHNOLOGY_NR ? state.currentFrequencyRange : 0;
+            int currentSignalStrength = state.currentSignalStrengths.get(currentRat);
+            expectedDurationsMs[currentRat][currentFrequencyRange][currentSignalStrength] += inc;
+        };
+
+        state.setOnBattery(false);
+        state.setModemActive(false);
+        state.setRatType(TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER);
+        state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_UNKNOWN);
+        state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER,
+                CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+        // While not on battery, the timers should not increase.
+        state.setModemActive(true);
+        incrementTime.accept(100);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+        state.setRatType(TelephonyManager.NETWORK_TYPE_NR, BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR);
+        incrementTime.accept(200);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+        state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR,
+                CellSignalStrength.SIGNAL_STRENGTH_GOOD);
+        incrementTime.accept(500);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+        state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_MMWAVE);
+        incrementTime.accept(300);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+        state.setRatType(TelephonyManager.NETWORK_TYPE_LTE,
+                BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE);
+        incrementTime.accept(400);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+        state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
+                CellSignalStrength.SIGNAL_STRENGTH_MODERATE);
+        incrementTime.accept(500);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+        // When set on battery, currently active state (RAT:LTE, Signal Strength:Moderate) should
+        // start counting up.
+        state.setOnBattery(true);
+        incrementTime.accept(600);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+        // Changing LTE signal strength should be tracked.
+        state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
+                CellSignalStrength.SIGNAL_STRENGTH_POOR);
+        incrementTime.accept(700);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+        state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
+                CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+        incrementTime.accept(800);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+        state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
+                CellSignalStrength.SIGNAL_STRENGTH_GOOD);
+        incrementTime.accept(900);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+        state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
+                CellSignalStrength.SIGNAL_STRENGTH_GREAT);
+        incrementTime.accept(1000);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+        // Change in the signal strength of nonactive RAT should not affect anything.
+        state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER,
+                CellSignalStrength.SIGNAL_STRENGTH_POOR);
+        incrementTime.accept(1100);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+        // Changing to OTHER Rat should start tracking the poor signal strength.
+        state.setRatType(TelephonyManager.NETWORK_TYPE_CDMA,
+                BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER);
+        incrementTime.accept(1200);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+        // Noting frequency change should not affect non NR Rat.
+        state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_HIGH);
+        incrementTime.accept(1300);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+        // Now the NR Rat, HIGH frequency range, good signal strength should start counting.
+        state.setRatType(TelephonyManager.NETWORK_TYPE_NR, BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR);
+        incrementTime.accept(1400);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+        // Noting frequency change should not affect non NR Rat.
+        state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_LOW);
+        incrementTime.accept(1500);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+        // Modem no longer active, should not be tracking any more.
+        state.setModemActive(false);
+        incrementTime.accept(1500);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+    }
+
     private void setFgState(int uid, boolean fgOn, MockBatteryStatsImpl bi) {
         // Note that noteUidProcessStateLocked uses ActivityManager process states.
         if (fgOn) {
@@ -1238,4 +1424,30 @@
                     bi.getScreenBrightnessTime(bin, currentTimeMs * 1000, STATS_SINCE_CHARGED));
         }
     }
+
+    private void checkPerStateActiveRadioDurations(long[][][] expectedDurationsMs,
+            BatteryStatsImpl bi, long currentTimeMs) {
+        for (int rat = 0; rat < expectedDurationsMs.length; rat++) {
+            final long[][] expectedRatDurationsMs = expectedDurationsMs[rat];
+            for (int freq = 0; freq < expectedRatDurationsMs.length; freq++) {
+                final long[] expectedFreqDurationsMs = expectedRatDurationsMs[freq];
+                for (int strength = 0; strength < expectedFreqDurationsMs.length; strength++) {
+                    final long expectedSignalStrengthDurationMs = expectedFreqDurationsMs[strength];
+                    final long actualDurationMs = bi.getActiveRadioDurationMs(rat, freq,
+                            strength, currentTimeMs);
+
+                    // Build a verbose fail message, just in case.
+                    final StringBuilder sb = new StringBuilder();
+                    sb.append("Wrong time in state for RAT:");
+                    sb.append(BatteryStats.RADIO_ACCESS_TECHNOLOGY_NAMES[rat]);
+                    sb.append(", frequency:");
+                    sb.append(ServiceState.frequencyRangeToString(freq));
+                    sb.append(", strength:");
+                    sb.append(strength);
+
+                    assertEquals(sb.toString(), expectedSignalStrengthDurationMs, actualDurationMs);
+                }
+            }
+        }
+    }
 }
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index e68b1ac..b3dcc34 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -334,6 +334,7 @@
         <permission name="android.permission.MANAGE_ACCESSIBILITY"/>
         <permission name="android.permission.MANAGE_DEVICE_ADMINS"/>
         <permission name="android.permission.MANAGE_GAME_MODE"/>
+        <permission name="android.permission.MANAGE_LOW_POWER_STANDBY" />
         <permission name="android.permission.MANAGE_ROLLBACKS"/>
         <permission name="android.permission.MANAGE_USB"/>
         <permission name="android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS"/>
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index 4ae0fc4..5a3a033 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -272,13 +272,13 @@
 
     <!-- fallback fonts -->
     <family lang="und-Arab" variant="elegant">
-        <font weight="400" style="normal" postScriptName="NotoNaskhArabic">
+        <font weight="400" style="normal">
             NotoNaskhArabic-Regular.ttf
         </font>
         <font weight="700" style="normal">NotoNaskhArabic-Bold.ttf</font>
     </family>
     <family lang="und-Arab" variant="compact">
-        <font weight="400" style="normal" postScriptName="NotoNaskhArabicUI">
+        <font weight="400" style="normal">
             NotoNaskhArabicUI-Regular.ttf
         </font>
         <font weight="700" style="normal">NotoNaskhArabicUI-Bold.ttf</font>
@@ -329,7 +329,7 @@
         <font weight="400" style="normal" postScriptName="NotoSansThai">NotoSansThai-Regular.ttf
         </font>
         <font weight="700" style="normal">NotoSansThai-Bold.ttf</font>
-        <font weight="400" style="normal" fallbackFor="serif" postScriptName="NotoSerifThai">
+        <font weight="400" style="normal" fallbackFor="serif">
             NotoSerifThai-Regular.ttf
         </font>
         <font weight="700" style="normal" fallbackFor="serif">NotoSerifThai-Bold.ttf</font>
@@ -923,16 +923,16 @@
         <font weight="700" style="normal">NotoSansKhmerUI-Bold.ttf</font>
     </family>
     <family lang="und-Laoo" variant="elegant">
-        <font weight="400" style="normal" postScriptName="NotoSansLao">NotoSansLao-Regular.ttf
+        <font weight="400" style="normal">NotoSansLao-Regular.ttf
         </font>
         <font weight="700" style="normal">NotoSansLao-Bold.ttf</font>
-        <font weight="400" style="normal" fallbackFor="serif" postScriptName="NotoSerifLao">
+        <font weight="400" style="normal" fallbackFor="serif">
             NotoSerifLao-Regular.ttf
         </font>
         <font weight="700" style="normal" fallbackFor="serif">NotoSerifLao-Bold.ttf</font>
     </family>
     <family lang="und-Laoo" variant="compact">
-        <font weight="400" style="normal" postScriptName="NotoSansLaoUI">NotoSansLaoUI-Regular.ttf
+        <font weight="400" style="normal">NotoSansLaoUI-Regular.ttf
         </font>
         <font weight="700" style="normal">NotoSansLaoUI-Bold.ttf</font>
     </family>
@@ -1013,7 +1013,7 @@
         </font>
     </family>
     <family lang="und-Cans">
-        <font weight="400" style="normal" postScriptName="NotoSansCanadianAboriginal">
+        <font weight="400" style="normal">
             NotoSansCanadianAboriginal-Regular.ttf
         </font>
     </family>
diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml
index 84cf285a..76b4036 100644
--- a/libs/WindowManager/Shell/res/values-te/strings.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings.xml
@@ -36,16 +36,16 @@
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ప్రత్యామ్నాయ డిస్‌ప్లేలో యాప్ పని చేయకపోవచ్చు."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ప్రత్యామ్నాయ డిస్‌ప్లేల్లో ప్రారంభానికి యాప్ మద్దతు లేదు."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"విభజన స్క్రీన్ విభాగిని"</string>
-    <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"ఎడమవైపు పూర్తి స్క్రీన్"</string>
+    <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"ఎడమవైపు ఫుల్-స్క్రీన్‌"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"ఎడమవైపు 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ఎడమవైపు 50%"</string>
     <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"ఎడమవైపు 30%"</string>
-    <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"కుడివైపు పూర్తి స్క్రీన్"</string>
-    <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"ఎగువ పూర్తి స్క్రీన్"</string>
+    <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"కుడివైపు ఫుల్-స్క్రీన్‌"</string>
+    <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"ఎగువ ఫుల్-స్క్రీన్‌"</string>
     <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"ఎగువ 70%"</string>
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ఎగువ 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"ఎగువ 30%"</string>
-    <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"దిగువ పూర్తి స్క్రీన్"</string>
+    <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"దిగువ ఫుల్-స్క్రీన్‌"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"వన్-హ్యాండెడ్ మోడ్‌ను ఉపయోగించడం"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"నిష్క్రమించడానికి, స్క్రీన్ కింది భాగం నుండి పైకి స్వైప్ చేయండి లేదా యాప్ పైన ఎక్కడైనా ట్యాప్ చేయండి"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"వన్-హ్యాండెడ్ మోడ్‌ను ప్రారంభిస్తుంది"</string>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index eda09e3..5ebdceb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -150,43 +150,45 @@
 
             if (inLandscape) {
                 final Rect leftHitRegion = new Rect();
-                final Rect leftDrawRegion = topOrLeftBounds;
                 final Rect rightHitRegion = new Rect();
-                final Rect rightDrawRegion = bottomOrRightBounds;
 
                 // If we have existing split regions use those bounds, otherwise split it 50/50
                 if (inSplitScreen) {
-                    // Add the divider bounds to each side since that counts for the hit region.
-                    leftHitRegion.set(topOrLeftBounds);
-                    leftHitRegion.right += dividerWidth / 2;
-                    rightHitRegion.set(bottomOrRightBounds);
-                    rightHitRegion.left -= dividerWidth / 2;
+                    // The bounds of the existing split will have a divider bar, the hit region
+                    // should include that space. Find the center of the divider bar:
+                    float centerX = topOrLeftBounds.right + (dividerWidth / 2);
+                    // Now set the hit regions using that center.
+                    leftHitRegion.set(displayRegion);
+                    leftHitRegion.right = (int) centerX;
+                    rightHitRegion.set(displayRegion);
+                    rightHitRegion.left = (int) centerX;
                 } else {
                     displayRegion.splitVertically(leftHitRegion, rightHitRegion);
                 }
 
-                mTargets.add(new Target(TYPE_SPLIT_LEFT, leftHitRegion, leftDrawRegion));
-                mTargets.add(new Target(TYPE_SPLIT_RIGHT, rightHitRegion, rightDrawRegion));
+                mTargets.add(new Target(TYPE_SPLIT_LEFT, leftHitRegion, topOrLeftBounds));
+                mTargets.add(new Target(TYPE_SPLIT_RIGHT, rightHitRegion, bottomOrRightBounds));
 
             } else {
                 final Rect topHitRegion = new Rect();
-                final Rect topDrawRegion = topOrLeftBounds;
                 final Rect bottomHitRegion = new Rect();
-                final Rect bottomDrawRegion = bottomOrRightBounds;
 
                 // If we have existing split regions use those bounds, otherwise split it 50/50
                 if (inSplitScreen) {
-                    // Add the divider bounds to each side since that counts for the hit region.
-                    topHitRegion.set(topOrLeftBounds);
-                    topHitRegion.bottom += dividerWidth / 2;
-                    bottomHitRegion.set(bottomOrRightBounds);
-                    bottomHitRegion.top -= dividerWidth / 2;
+                    // The bounds of the existing split will have a divider bar, the hit region
+                    // should include that space. Find the center of the divider bar:
+                    float centerX = topOrLeftBounds.bottom + (dividerWidth / 2);
+                    // Now set the hit regions using that center.
+                    topHitRegion.set(displayRegion);
+                    topHitRegion.bottom = (int) centerX;
+                    bottomHitRegion.set(displayRegion);
+                    bottomHitRegion.top = (int) centerX;
                 } else {
                     displayRegion.splitHorizontally(topHitRegion, bottomHitRegion);
                 }
 
-                mTargets.add(new Target(TYPE_SPLIT_TOP, topHitRegion, topDrawRegion));
-                mTargets.add(new Target(TYPE_SPLIT_BOTTOM, bottomHitRegion, bottomDrawRegion));
+                mTargets.add(new Target(TYPE_SPLIT_TOP, topHitRegion, topOrLeftBounds));
+                mTargets.add(new Target(TYPE_SPLIT_BOTTOM, bottomHitRegion, bottomOrRightBounds));
             }
         } else {
             // Split-screen not allowed, so only show the fullscreen target
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 fd3be2b..d44db49 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
@@ -24,7 +24,6 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
 import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
@@ -156,10 +155,6 @@
         }
     }
 
-    public boolean hasDropTarget() {
-        return mCurrentTarget != null;
-    }
-
     public boolean hasDropped() {
         return mHasDropped;
     }
@@ -271,6 +266,9 @@
      * Updates the visible drop target as the user drags.
      */
     public void update(DragEvent event) {
+        if (mHasDropped) {
+            return;
+        }
         // Find containing region, if the same as mCurrentRegion, then skip, otherwise, animate the
         // visibility of the current region
         DragAndDropPolicy.Target target = mPolicy.getTargetAtLocation(
@@ -286,7 +284,8 @@
                 animateHighlight(target);
             } else {
                 // Switching between targets
-                animateHighlight(target);
+                mDropZoneView1.animateSwitch();
+                mDropZoneView2.animateSwitch();
             }
             mCurrentTarget = target;
         }
@@ -323,7 +322,7 @@
                 : DISABLE_NONE);
         mDropZoneView1.setShowingMargin(visible);
         mDropZoneView2.setShowingMargin(visible);
-        ObjectAnimator animator = mDropZoneView1.getAnimator();
+        Animator animator = mDropZoneView1.getAnimator();
         if (animCompleteCallback != null) {
             if (animator != null) {
                 animator.addListener(new AnimatorListenerAdapter() {
@@ -343,17 +342,11 @@
         if (target.type == DragAndDropPolicy.Target.TYPE_SPLIT_LEFT
                 || target.type == DragAndDropPolicy.Target.TYPE_SPLIT_TOP) {
             mDropZoneView1.setShowingHighlight(true);
-            mDropZoneView1.setShowingSplash(false);
-
             mDropZoneView2.setShowingHighlight(false);
-            mDropZoneView2.setShowingSplash(true);
         } else if (target.type == DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT
                 || target.type == DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM) {
             mDropZoneView1.setShowingHighlight(false);
-            mDropZoneView1.setShowingSplash(true);
-
             mDropZoneView2.setShowingHighlight(true);
-            mDropZoneView2.setShowingSplash(false);
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
index 2f47af5..a3ee8ae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
@@ -18,6 +18,7 @@
 
 import static com.android.wm.shell.animation.Interpolators.FAST_OUT_SLOW_IN;
 
+import android.animation.Animator;
 import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.graphics.Canvas;
@@ -27,7 +28,6 @@
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
-import android.util.IntProperty;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
@@ -43,8 +43,8 @@
  */
 public class DropZoneView extends FrameLayout {
 
-    private static final int SPLASHSCREEN_ALPHA_INT = (int) (255 * 0.90f);
-    private static final int HIGHLIGHT_ALPHA_INT = 255;
+    private static final float SPLASHSCREEN_ALPHA = 0.90f;
+    private static final float HIGHLIGHT_ALPHA = 1f;
     private static final int MARGIN_ANIMATION_ENTER_DURATION = 400;
     private static final int MARGIN_ANIMATION_EXIT_DURATION = 250;
 
@@ -61,54 +61,27 @@
                 }
             };
 
-    private static final IntProperty<ColorDrawable> SPLASHSCREEN_ALPHA =
-            new IntProperty<ColorDrawable>("splashscreen") {
-                @Override
-                public void setValue(ColorDrawable d, int alpha) {
-                    d.setAlpha(alpha);
-                }
-
-                @Override
-                public Integer get(ColorDrawable d) {
-                    return d.getAlpha();
-                }
-            };
-
-    private static final IntProperty<ColorDrawable> HIGHLIGHT_ALPHA =
-            new IntProperty<ColorDrawable>("highlight") {
-                @Override
-                public void setValue(ColorDrawable d, int alpha) {
-                    d.setAlpha(alpha);
-                }
-
-                @Override
-                public Integer get(ColorDrawable d) {
-                    return d.getAlpha();
-                }
-            };
-
     private final Path mPath = new Path();
     private final float[] mContainerMargin = new float[4];
     private float mCornerRadius;
     private float mBottomInset;
     private int mMarginColor; // i.e. color used for negative space like the container insets
-    private int mHighlightColor;
 
     private boolean mShowingHighlight;
     private boolean mShowingSplash;
     private boolean mShowingMargin;
 
-    // TODO: might be more seamless to animate between splash/highlight color instead of 2 separate
-    private ObjectAnimator mSplashAnimator;
-    private ObjectAnimator mHighlightAnimator;
+    private int mSplashScreenColor;
+    private int mHighlightColor;
+
+    private ObjectAnimator mBackgroundAnimator;
     private ObjectAnimator mMarginAnimator;
     private float mMarginPercent;
 
     // Renders a highlight or neutral transparent color
-    private ColorDrawable mDropZoneDrawable;
+    private ColorDrawable mColorDrawable;
     // Renders the translucent splashscreen with the app icon in the middle
     private ImageView mSplashScreenView;
-    private ColorDrawable mSplashBackgroundDrawable;
     // Renders the margin / insets around the dropzone container
     private MarginView mMarginView;
 
@@ -130,19 +103,14 @@
 
         mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
         mMarginColor = getResources().getColor(R.color.taskbar_background);
-        mHighlightColor = getResources().getColor(android.R.color.system_accent1_500);
-
-        mDropZoneDrawable = new ColorDrawable();
-        mDropZoneDrawable.setColor(mHighlightColor);
-        mDropZoneDrawable.setAlpha(0);
-        setBackgroundDrawable(mDropZoneDrawable);
+        int c = getResources().getColor(android.R.color.system_accent1_500);
+        mHighlightColor =  Color.argb(HIGHLIGHT_ALPHA, Color.red(c), Color.green(c), Color.blue(c));
+        mSplashScreenColor = Color.argb(SPLASHSCREEN_ALPHA, 0, 0, 0);
+        mColorDrawable = new ColorDrawable();
+        setBackgroundDrawable(mColorDrawable);
 
         mSplashScreenView = new ImageView(context);
         mSplashScreenView.setScaleType(ImageView.ScaleType.CENTER);
-        mSplashBackgroundDrawable = new ColorDrawable();
-        mSplashBackgroundDrawable.setColor(Color.WHITE);
-        mSplashBackgroundDrawable.setAlpha(SPLASHSCREEN_ALPHA_INT);
-        mSplashScreenView.setBackgroundDrawable(mSplashBackgroundDrawable);
         addView(mSplashScreenView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                 ViewGroup.LayoutParams.MATCH_PARENT));
         mSplashScreenView.setAlpha(0f);
@@ -157,10 +125,6 @@
         mMarginColor = getResources().getColor(R.color.taskbar_background);
         mHighlightColor = getResources().getColor(android.R.color.system_accent1_500);
 
-        final int alpha = mDropZoneDrawable.getAlpha();
-        mDropZoneDrawable.setColor(mHighlightColor);
-        mDropZoneDrawable.setAlpha(alpha);
-
         if (mMarginPercent > 0) {
             mMarginView.invalidate();
         }
@@ -187,38 +151,39 @@
     }
 
     /** Sets the color and icon to use for the splashscreen when shown. */
-    public void setAppInfo(int splashScreenColor, Drawable appIcon) {
-        mSplashBackgroundDrawable.setColor(splashScreenColor);
+    public void setAppInfo(int color, Drawable appIcon) {
+        Color c = Color.valueOf(color);
+        mSplashScreenColor = Color.argb(SPLASHSCREEN_ALPHA, c.red(), c.green(), c.blue());
         mSplashScreenView.setImageDrawable(appIcon);
     }
 
     /** @return an active animator for this view if one exists. */
     @Nullable
-    public ObjectAnimator getAnimator() {
+    public Animator getAnimator() {
         if (mMarginAnimator != null && mMarginAnimator.isRunning()) {
             return mMarginAnimator;
-        } else if (mHighlightAnimator != null && mHighlightAnimator.isRunning()) {
-            return mHighlightAnimator;
-        } else if (mSplashAnimator != null && mSplashAnimator.isRunning()) {
-            return mSplashAnimator;
+        } else if (mBackgroundAnimator != null && mBackgroundAnimator.isRunning()) {
+            return mBackgroundAnimator;
         }
         return null;
     }
 
-    /** Animates the splashscreen to show or hide. */
-    public void setShowingSplash(boolean showingSplash) {
-        if (mShowingSplash != showingSplash) {
-            mShowingSplash = showingSplash;
-            animateSplashToState();
-        }
+    /** Animates between highlight and splashscreen depending on current state. */
+    public void animateSwitch() {
+        mShowingHighlight = !mShowingHighlight;
+        mShowingSplash = !mShowingHighlight;
+        final int newColor = mShowingHighlight ? mHighlightColor : mSplashScreenColor;
+        animateBackground(mColorDrawable.getColor(), newColor);
+        animateSplashScreenIcon();
     }
 
     /** Animates the highlight indicating the zone is hovered on or not. */
     public void setShowingHighlight(boolean showingHighlight) {
-        if (mShowingHighlight != showingHighlight) {
-            mShowingHighlight = showingHighlight;
-            animateHighlightToState();
-        }
+        mShowingHighlight = showingHighlight;
+        mShowingSplash = !mShowingHighlight;
+        final int newColor = mShowingHighlight ? mHighlightColor : mSplashScreenColor;
+        animateBackground(Color.TRANSPARENT, newColor);
+        animateSplashScreenIcon();
     }
 
     /** Animates the margins around the drop zone to show or hide. */
@@ -228,40 +193,31 @@
             animateMarginToState();
         }
         if (!mShowingMargin) {
-            setShowingHighlight(false);
-            setShowingSplash(false);
+            mShowingHighlight = false;
+            mShowingSplash = false;
+            animateBackground(mColorDrawable.getColor(), Color.TRANSPARENT);
+            animateSplashScreenIcon();
         }
     }
 
-    private void animateSplashToState() {
-        if (mSplashAnimator != null) {
-            mSplashAnimator.cancel();
+    private void animateBackground(int startColor, int endColor) {
+        if (mBackgroundAnimator != null) {
+            mBackgroundAnimator.cancel();
         }
-        mSplashAnimator = ObjectAnimator.ofInt(mSplashBackgroundDrawable,
-                SPLASHSCREEN_ALPHA,
-                mSplashBackgroundDrawable.getAlpha(),
-                mShowingSplash ? SPLASHSCREEN_ALPHA_INT : 0);
-        if (!mShowingSplash) {
-            mSplashAnimator.setInterpolator(FAST_OUT_SLOW_IN);
+        mBackgroundAnimator = ObjectAnimator.ofArgb(mColorDrawable,
+                "color",
+                startColor,
+                endColor);
+        if (!mShowingSplash && !mShowingHighlight) {
+            mBackgroundAnimator.setInterpolator(FAST_OUT_SLOW_IN);
         }
-        mSplashAnimator.start();
+        mBackgroundAnimator.start();
+    }
+
+    private void animateSplashScreenIcon() {
         mSplashScreenView.animate().alpha(mShowingSplash ? 1f : 0f).start();
     }
 
-    private void animateHighlightToState() {
-        if (mHighlightAnimator != null) {
-            mHighlightAnimator.cancel();
-        }
-        mHighlightAnimator = ObjectAnimator.ofInt(mDropZoneDrawable,
-                HIGHLIGHT_ALPHA,
-                mDropZoneDrawable.getAlpha(),
-                mShowingHighlight ? HIGHLIGHT_ALPHA_INT : 0);
-        if (!mShowingHighlight) {
-            mHighlightAnimator.setInterpolator(FAST_OUT_SLOW_IN);
-        }
-        mHighlightAnimator.start();
-    }
-
     private void animateMarginToState() {
         if (mMarginAnimator != null) {
             mMarginAnimator.cancel();
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 13e81bd..79c8a87 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
@@ -371,10 +371,14 @@
                 }
 
                 if (a.getShowBackground()) {
-                    // use the window's background color if provided as the background color for the
-                    // animation - the top most window with a valid background color and
-                    // showBackground set takes precedence.
-                    if (change.getBackgroundColor() != 0) {
+                    if (info.getAnimationOptions().getBackgroundColor() != 0) {
+                        // If available use the background color provided through AnimationOptions
+                        backgroundColorForTransition =
+                                info.getAnimationOptions().getBackgroundColor();
+                    } else if (change.getBackgroundColor() != 0) {
+                        // Otherwise default to the window's background color if provided through
+                        // the theme as the background color for the animation - the top most window
+                        // with a valid background color and showBackground set takes precedence.
                         backgroundColorForTransition = change.getBackgroundColor();
                     }
                 }
diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h
index 8ad8abc..25ed935 100644
--- a/libs/hwui/renderthread/DrawFrameTask.h
+++ b/libs/hwui/renderthread/DrawFrameTask.h
@@ -16,19 +16,18 @@
 #ifndef DRAWFRAMETASK_H
 #define DRAWFRAMETASK_H
 
-#include <optional>
-#include <vector>
-
-#include <performance_hint_private.h>
+#include <android/performance_hint.h>
 #include <utils/Condition.h>
 #include <utils/Mutex.h>
 #include <utils/StrongPointer.h>
 
-#include "RenderTask.h"
+#include <optional>
+#include <vector>
 
 #include "../FrameInfo.h"
 #include "../Rect.h"
 #include "../TreeInfo.h"
+#include "RenderTask.h"
 
 namespace android {
 namespace uirenderer {
diff --git a/libs/tracingproxy/Android.bp b/libs/tracingproxy/Android.bp
index 7126bfa..23d107b 100644
--- a/libs/tracingproxy/Android.bp
+++ b/libs/tracingproxy/Android.bp
@@ -37,6 +37,7 @@
 
     srcs: [
         ":ITracingServiceProxy.aidl",
+        ":TraceReportParams.aidl",
     ],
 
     shared_libs: [
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 41666c7..75236f4 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -1851,6 +1851,7 @@
      *
      * @hide
      */
+    @SystemApi
     @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS)
     public int getClientPid(@NonNull String sessionId) {
         return getClientPidInternal(sessionId);
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index feae606..18b779f 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -87,6 +87,7 @@
         "android.hardware.drm@1.4",
         "android.hidl.memory@1.0",
         "android.hidl.token@1.0-utils",
+        "android.hardware.drm-V1-ndk",
     ],
 
     header_libs: [
diff --git a/media/native/midi/amidi.cpp b/media/native/midi/amidi.cpp
index aa076e8..fd8a06d 100644
--- a/media/native/midi/amidi.cpp
+++ b/media/native/midi/amidi.cpp
@@ -401,10 +401,14 @@
 
 ssize_t AMIDI_API AMidiInputPort_sendWithTimestamp(const AMidiInputPort *inputPort,
         const uint8_t *data, size_t numBytes, int64_t timestamp) {
-    if (inputPort == nullptr || data == nullptr) {
+    if (inputPort == nullptr || data == nullptr || numBytes < 0 || timestamp < 0) {
         return AMEDIA_ERROR_INVALID_PARAMETER;
     }
 
+    if (numBytes == 0) {
+        return 0;
+    }
+
     // AMIDI_logBuffer(data, numBytes);
 
     uint8_t writeBuffer[AMIDI_BUFFER_SIZE + AMIDI_PACKET_OVERHEAD];
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 35c794e..f9a1774 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -315,18 +315,18 @@
     AThermal_registerThermalStatusListener; # introduced=30
     AThermal_unregisterThermalStatusListener; # introduced=30
     AThermal_getThermalHeadroom; # introduced=31
+    APerformanceHint_getManager; # introduced=Tiramisu
+    APerformanceHint_createSession; # introduced=Tiramisu
+    APerformanceHint_getPreferredUpdateRateNanos; # introduced=Tiramisu
+    APerformanceHint_updateTargetWorkDuration; # introduced=Tiramisu
+    APerformanceHint_reportActualWorkDuration; # introduced=Tiramisu
+    APerformanceHint_closeSession; # introduced=Tiramisu
   local:
     *;
 };
 
 LIBANDROID_PLATFORM {
   global:
-    APerformanceHint_getManager;
-    APerformanceHint_createSession;
-    APerformanceHint_getPreferredUpdateRateNanos;
-    APerformanceHint_updateTargetWorkDuration;
-    APerformanceHint_reportActualWorkDuration;
-    APerformanceHint_closeSession;
     APerformanceHint_setIHintManagerForTesting;
     extern "C++" {
         ASurfaceControl_registerSurfaceStatsListener*;
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index 51a0c99..0c36051 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -16,17 +16,18 @@
 
 #define LOG_TAG "perf_hint"
 
-#include <utility>
-#include <vector>
-
 #include <android/os/IHintManager.h>
 #include <android/os/IHintSession.h>
+#include <android/performance_hint.h>
 #include <binder/Binder.h>
 #include <binder/IBinder.h>
 #include <binder/IServiceManager.h>
 #include <performance_hint_private.h>
 #include <utils/SystemClock.h>
 
+#include <utility>
+#include <vector>
+
 using namespace android;
 using namespace android::os;
 
diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
index 284e9ee..b17850e 100644
--- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
+++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
@@ -18,10 +18,12 @@
 
 #include <android/os/IHintManager.h>
 #include <android/os/IHintSession.h>
+#include <android/performance_hint.h>
 #include <binder/IBinder.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <performance_hint_private.h>
+
 #include <memory>
 #include <vector>
 
diff --git a/omapi/OWNERS b/omapi/OWNERS
new file mode 100644
index 0000000..5682fd3
--- /dev/null
+++ b/omapi/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 456592
+
+zachoverflow@google.com
+alisher@google.com
+jackcwyu@google.com
diff --git a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
index 84adef5..5ce7e59 100644
--- a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
+++ b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
@@ -41,6 +41,7 @@
 import android.net.NetworkTemplate;
 import android.net.UnderlyingNetworkInfo;
 import android.net.netstats.IUsageCallback;
+import android.net.netstats.NetworkStatsDataMigrationUtils;
 import android.net.netstats.provider.INetworkStatsProviderCallback;
 import android.net.netstats.provider.NetworkStatsProvider;
 import android.os.Build;
@@ -126,17 +127,12 @@
     private final INetworkStatsService mService;
 
     /**
-     * Type constants for reading different types of Data Usage.
+     * @deprecated Use {@link NetworkStatsDataMigrationUtils#PREFIX_XT}
+     * instead.
      * @hide
      */
-    // @SystemApi(client = MODULE_LIBRARIES)
+    @Deprecated
     public static final String PREFIX_DEV = "dev";
-    /** @hide */
-    public static final String PREFIX_XT = "xt";
-    /** @hide */
-    public static final String PREFIX_UID = "uid";
-    /** @hide */
-    public static final String PREFIX_UID_TAG = "uid_tag";
 
     /** @hide */
     public static final int FLAG_POLL_ON_OPEN = 1 << 0;
diff --git a/packages/ConnectivityT/framework-t/src/com/android/server/NetworkManagementSocketTagger.java b/packages/ConnectivityT/framework-t/src/com/android/server/NetworkManagementSocketTagger.java
index 1eb52fb..8bb12a6d 100644
--- a/packages/ConnectivityT/framework-t/src/com/android/server/NetworkManagementSocketTagger.java
+++ b/packages/ConnectivityT/framework-t/src/com/android/server/NetworkManagementSocketTagger.java
@@ -111,21 +111,6 @@
         public int statsUid = -1;
     }
 
-    public static void setKernelCounterSet(int uid, int counterSet) {
-        final int errno = native_setCounterSet(counterSet, uid);
-        if (errno < 0) {
-            Log.w(TAG, "setKernelCountSet(" + uid + ", " + counterSet + ") failed with errno "
-                    + errno);
-        }
-    }
-
-    public static void resetKernelUidStats(int uid) {
-        int errno = native_deleteTagData(0, uid);
-        if (errno < 0) {
-            Log.w(TAG, "problem clearing counters for uid " + uid + " : errno " + errno);
-        }
-    }
-
     /**
      * Convert {@code /proc/} tag format to {@link Integer}. Assumes incoming
      * format like {@code 0x7fffffff00000000}.
@@ -141,6 +126,4 @@
 
     private static native int native_tagSocketFd(FileDescriptor fd, int tag, int uid);
     private static native int native_untagSocketFd(FileDescriptor fd);
-    private static native int native_setCounterSet(int uid, int counterSetNum);
-    private static native int native_deleteTagData(int tag, int uid);
 }
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
index 8e584d0..9f3371b 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
@@ -20,9 +20,6 @@
 import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
 import static android.Manifest.permission.UPDATE_DEVICE_STATS;
 import static android.app.usage.NetworkStatsManager.PREFIX_DEV;
-import static android.app.usage.NetworkStatsManager.PREFIX_UID;
-import static android.app.usage.NetworkStatsManager.PREFIX_UID_TAG;
-import static android.app.usage.NetworkStatsManager.PREFIX_XT;
 import static android.content.Intent.ACTION_SHUTDOWN;
 import static android.content.Intent.ACTION_UID_REMOVED;
 import static android.content.Intent.ACTION_USER_REMOVED;
@@ -50,6 +47,9 @@
 import static android.net.TrafficStats.MB_IN_BYTES;
 import static android.net.TrafficStats.UID_TETHERING;
 import static android.net.TrafficStats.UNSUPPORTED;
+import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID;
+import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID_TAG;
+import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_XT;
 import static android.os.Trace.TRACE_TAG_NETWORK;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 import static android.text.format.DateUtils.DAY_IN_MILLIS;
@@ -59,7 +59,6 @@
 
 import static com.android.net.module.util.NetworkCapabilitiesUtils.getDisplayTransport;
 import static com.android.net.module.util.NetworkStatsUtils.LIMIT_GLOBAL_ALERT;
-import static com.android.server.NetworkManagementSocketTagger.resetKernelUidStats;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -121,6 +120,7 @@
 import android.service.NetworkInterfaceProto;
 import android.service.NetworkStatsServiceDumpProto;
 import android.system.ErrnoException;
+import android.system.Os;
 import android.telephony.PhoneStateListener;
 import android.telephony.SubscriptionPlan;
 import android.text.TextUtils;
@@ -546,6 +546,10 @@
                 return null;
             }
         }
+
+        public TagStatsDeleter getTagStatsDeleter() {
+            return NetworkStatsService::nativeDeleteTagData;
+        }
     }
 
     /**
@@ -1801,7 +1805,10 @@
 
         // Clear kernel stats associated with UID
         for (int uid : uids) {
-            resetKernelUidStats(uid);
+            final int ret = mDeps.getTagStatsDeleter().deleteTagData(uid);
+            if (ret < 0) {
+                Log.w(TAG, "problem clearing counters for uid " + uid + ": " + Os.strerror(-ret));
+            }
         }
     }
 
@@ -2380,4 +2387,12 @@
     private static native long nativeGetTotalStat(int type);
     private static native long nativeGetIfaceStat(String iface, int type);
     private static native long nativeGetUidStat(int uid, int type);
+
+    // TODO: use BpfNetMaps to delete tag data and remove this.
+    @VisibleForTesting
+    interface TagStatsDeleter {
+        int deleteTagData(int uid);
+    }
+
+    private static native int nativeDeleteTagData(int uid);
 }
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index fcf2282..684f4de 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -50,6 +50,7 @@
         "SettingsLibSettingsTransition",
         "SettingsLibActivityEmbedding",
         "SettingsLibButtonPreference",
+        "setupdesign",
     ],
 
     // ANDROIDMK TRANSLATION ERROR: unsupported assignment to LOCAL_SHARED_JAVA_LIBRARIES
diff --git a/packages/SettingsLib/AndroidManifest.xml b/packages/SettingsLib/AndroidManifest.xml
index a347345..13f8a37 100644
--- a/packages/SettingsLib/AndroidManifest.xml
+++ b/packages/SettingsLib/AndroidManifest.xml
@@ -18,4 +18,10 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.settingslib">
 
+    <application>
+        <activity
+            android:name="com.android.settingslib.users.AvatarPickerActivity"
+            android:theme="@style/SudThemeGlifV2.DayNight"/>
+    </application>
+
 </manifest>
diff --git a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
index e51bb45..3eb6ea9 100644
--- a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
+++ b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
@@ -42,6 +42,7 @@
     @VisibleForTesting
     View.OnClickListener mLearnMoreListener;
     private CharSequence mContentDescription;
+    private CharSequence mLearnMoreText;
     private CharSequence mLearnMoreContentDescription;
     private FooterLearnMoreSpan mLearnMoreSpan;
 
@@ -69,7 +70,12 @@
         TextView learnMore = holder.itemView.findViewById(R.id.settingslib_learn_more);
         if (learnMore != null && mLearnMoreListener != null) {
             learnMore.setVisibility(View.VISIBLE);
-            SpannableString learnMoreText = new SpannableString(learnMore.getText());
+            if (TextUtils.isEmpty(mLearnMoreText)) {
+                mLearnMoreText = learnMore.getText();
+            } else {
+                learnMore.setText(mLearnMoreText);
+            }
+            SpannableString learnMoreText = new SpannableString(mLearnMoreText);
             if (mLearnMoreSpan != null) {
                 learnMoreText.removeSpan(mLearnMoreSpan);
             }
@@ -123,6 +129,18 @@
     }
 
     /**
+     * Sets the learn more text.
+     *
+     * @param learnMoreText The string of the learn more text.
+     */
+    public void setLearnMoreText(CharSequence learnMoreText) {
+        if (!TextUtils.equals(mLearnMoreText, learnMoreText)) {
+            mLearnMoreText = learnMoreText;
+            notifyChanged();
+        }
+    }
+
+    /**
      * To set content description of the learn more text. This can use for talkback
      * environment if developer wants to have a customization content.
      *
diff --git a/packages/SettingsLib/res/drawable/add_a_photo_circled.xml b/packages/SettingsLib/res/drawable/add_a_photo_circled.xml
new file mode 100644
index 0000000..bcfd221
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/add_a_photo_circled.xml
@@ -0,0 +1,28 @@
+<!--
+  ~ 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.
+  -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <shape android:shape="oval">
+            <solid android:color="?android:attr/colorAccent"/>
+        </shape>
+    </item>
+    <item
+        android:left="@dimen/add_a_photo_circled_padding"
+        android:right="@dimen/add_a_photo_circled_padding"
+        android:top="@dimen/add_a_photo_circled_padding"
+        android:bottom="@dimen/add_a_photo_circled_padding"
+        android:drawable="@drawable/ic_add_a_photo"/>
+</layer-list>
diff --git a/packages/SettingsLib/res/drawable/avatar_choose_photo_circled.xml b/packages/SettingsLib/res/drawable/avatar_choose_photo_circled.xml
new file mode 100644
index 0000000..97aec74
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/avatar_choose_photo_circled.xml
@@ -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.
+  -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <shape android:shape="oval">
+            <stroke
+                android:width="2dp"
+                android:color="?android:attr/colorPrimary"/>
+        </shape>
+    </item>
+    <item
+        android:left="@dimen/avatar_picker_icon_inset"
+        android:right="@dimen/avatar_picker_icon_inset"
+        android:top="@dimen/avatar_picker_icon_inset"
+        android:bottom="@dimen/avatar_picker_icon_inset"
+        android:drawable="@drawable/ic_avatar_choose_photo"/>
+</layer-list>
diff --git a/packages/SettingsLib/res/drawable/avatar_selector.xml b/packages/SettingsLib/res/drawable/avatar_selector.xml
new file mode 100644
index 0000000..ccde597
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/avatar_selector.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    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.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_selected="true">
+        <shape android:shape="oval">
+            <stroke
+                android:color="?android:attr/colorPrimary"
+                android:width="@dimen/avatar_picker_padding"/>
+        </shape>
+    </item>
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/drawable/avatar_take_photo_circled.xml b/packages/SettingsLib/res/drawable/avatar_take_photo_circled.xml
new file mode 100644
index 0000000..7033aae
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/avatar_take_photo_circled.xml
@@ -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.
+  -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <shape android:shape="oval">
+            <stroke
+                android:width="2dp"
+                android:color="?android:attr/colorPrimary"/>
+        </shape>
+    </item>
+    <item
+        android:left="@dimen/avatar_picker_icon_inset"
+        android:right="@dimen/avatar_picker_icon_inset"
+        android:top="@dimen/avatar_picker_icon_inset"
+        android:bottom="@dimen/avatar_picker_icon_inset"
+        android:drawable="@drawable/ic_avatar_take_photo"/>
+</layer-list>
diff --git a/packages/SettingsLib/res/drawable/ic_account_circle_outline.xml b/packages/SettingsLib/res/drawable/ic_account_circle_outline.xml
new file mode 100644
index 0000000..0cc54b6
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_account_circle_outline.xml
@@ -0,0 +1,25 @@
+<!--
+    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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+    <path
+        android:fillColor="?android:attr/colorPrimary"
+        android:pathData="M5.85,17.1q1.275,-0.975 2.85,-1.538Q10.275,15 12,15q1.725,0 3.3,0.563 1.575,0.562 2.85,1.537 0.875,-1.025 1.363,-2.325Q20,13.475 20,12q0,-3.325 -2.337,-5.662Q15.325,4 12,4T6.338,6.338Q4,8.675 4,12q0,1.475 0.487,2.775 0.488,1.3 1.363,2.325zM12,13q-1.475,0 -2.488,-1.012Q8.5,10.975 8.5,9.5t1.012,-2.487Q10.525,6 12,6t2.488,1.013Q15.5,8.024 15.5,9.5t-1.012,2.488Q13.475,13 12,13zM12,22q-2.075,0 -3.9,-0.788 -1.825,-0.787 -3.175,-2.137 -1.35,-1.35 -2.137,-3.175Q2,14.075 2,12t0.788,-3.9q0.787,-1.825 2.137,-3.175 1.35,-1.35 3.175,-2.137Q9.925,2 12,2t3.9,0.788q1.825,0.787 3.175,2.137 1.35,1.35 2.137,3.175Q22,9.925 22,12t-0.788,3.9q-0.787,1.825 -2.137,3.175 -1.35,1.35 -3.175,2.137Q14.075,22 12,22zM12,20q1.325,0 2.5,-0.387 1.175,-0.388 2.15,-1.113 -0.975,-0.725 -2.15,-1.113Q13.325,17 12,17t-2.5,0.387q-1.175,0.388 -2.15,1.113 0.975,0.725 2.15,1.113Q10.675,20 12,20zM12,11q0.65,0 1.075,-0.425 0.425,-0.425 0.425,-1.075 0,-0.65 -0.425,-1.075Q12.65,8 12,8q-0.65,0 -1.075,0.425Q10.5,8.85 10.5,9.5q0,0.65 0.425,1.075Q11.35,11 12,11zM12,9.5zM12,18.5z"/>
+</vector>
+
diff --git a/packages/SettingsLib/res/drawable/ic_add_a_photo.xml b/packages/SettingsLib/res/drawable/ic_add_a_photo.xml
new file mode 100644
index 0000000..4e35503
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_add_a_photo.xml
@@ -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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+    <path
+        android:fillColor="?android:attr/colorPrimary"
+        android:pathData="M11.5,17.5q1.875,0 3.188,-1.313Q16,14.876 16,13q0,-1.875 -1.313,-3.188Q13.376,8.5 11.5,8.5q-1.875,0 -3.188,1.313Q7,11.124 7,13q0,1.875 1.313,3.188Q9.624,17.5 11.5,17.5zM3.5,21q-0.825,0 -1.413,-0.587Q1.5,19.825 1.5,19L1.5,7q0,-0.825 0.587,-1.412Q2.675,5 3.5,5h3.15L8.5,3h6v4h-11v12h16v-9h2v9q0,0.825 -0.587,1.413Q20.325,21 19.5,21zM18.5,8L18.5,6h-2L16.5,4h2L18.5,2h2v2h2v2h-2v2zM11.5,13z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_avatar_choose_photo.xml b/packages/SettingsLib/res/drawable/ic_avatar_choose_photo.xml
new file mode 100644
index 0000000..b85fdc2
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_avatar_choose_photo.xml
@@ -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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+    <path
+        android:fillColor="?android:attr/colorPrimary"
+        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>
diff --git a/packages/SettingsLib/res/drawable/ic_avatar_take_photo.xml b/packages/SettingsLib/res/drawable/ic_avatar_take_photo.xml
new file mode 100644
index 0000000..5c56276
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_avatar_take_photo.xml
@@ -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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+    <path
+        android:fillColor="?android:attr/colorPrimary"
+        android:pathData="M12,17.5q1.875,0 3.188,-1.313Q16.5,14.876 16.5,13q0,-1.875 -1.313,-3.188Q13.876,8.5 12,8.5q-1.875,0 -3.188,1.313Q7.5,11.124 7.5,13q0,1.875 1.313,3.188Q10.124,17.5 12,17.5zM4,21q-0.825,0 -1.413,-0.587Q2,19.825 2,19L2,7q0,-0.825 0.587,-1.412Q3.175,5 4,5h3.15L9,3h6l1.85,2L20,5q0.825,0 1.413,0.588Q22,6.175 22,7v12q0,0.825 -0.587,1.413Q20.825,21 20,21zM20,19L20,7L4,7v12zM4,19L4,7v12z"/>
+</vector>
diff --git a/packages/SettingsLib/res/layout/avatar_item.xml b/packages/SettingsLib/res/layout/avatar_item.xml
new file mode 100644
index 0000000..c52f664
--- /dev/null
+++ b/packages/SettingsLib/res/layout/avatar_item.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    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.
+  -->
+<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/avatar_image"
+    android:layout_height="@dimen/avatar_size_in_picker"
+    android:layout_width="@dimen/avatar_size_in_picker"
+    android:layout_margin="@dimen/avatar_picker_margin"
+    android:layout_gravity="center"
+    android:padding="@dimen/avatar_picker_padding"
+    android:background="@drawable/avatar_selector"/>
diff --git a/packages/SettingsLib/res/layout/avatar_picker.xml b/packages/SettingsLib/res/layout/avatar_picker.xml
new file mode 100644
index 0000000..2d40bd0
--- /dev/null
+++ b/packages/SettingsLib/res/layout/avatar_picker.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    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.
+  -->
+<com.google.android.setupdesign.GlifLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/glif_layout"
+    android:layout_height="match_parent"
+    android:layout_width="match_parent"
+    android:icon="@drawable/ic_account_circle_outline"
+    app:sucUsePartnerResource="true"
+    app:sucHeaderText="@string/avatar_picker_title">
+
+    <LinearLayout
+        android:layout_height="match_parent"
+        android:layout_width="match_parent"
+        android:gravity="center_horizontal"
+        style="@style/SudContentFrame">
+        <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/avatar_grid"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"/>
+    </LinearLayout>
+
+</com.google.android.setupdesign.GlifLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml b/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
index f66ff00..c8ddcc8 100644
--- a/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
+++ b/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
@@ -18,24 +18,32 @@
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:baselineAligned="false"
-    android:orientation="horizontal"
+    android:orientation="vertical"
     android:padding="16dp">
 
-    <ImageView
-        android:id="@+id/user_photo"
-        android:layout_width="56dp"
-        android:layout_height="56dp"
-        android:layout_gravity="bottom"
-        android:contentDescription="@string/user_image_photo_selector"
-        android:background="@*android:drawable/spinner_background_holo_dark"
-        android:scaleType="fitCenter"/>
+    <FrameLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center">
+        <ImageView
+            android:id="@+id/user_photo"
+            android:layout_width="@dimen/user_photo_size_in_profile_info_dialog"
+            android:layout_height="@dimen/user_photo_size_in_profile_info_dialog"
+            android:contentDescription="@string/user_image_photo_selector"
+            android:scaleType="fitCenter"/>
+        <ImageView
+            android:id="@+id/add_a_photo_icon"
+            android:layout_width="@dimen/add_a_photo_icon_size_in_profile_info_dialog"
+            android:layout_height="@dimen/add_a_photo_icon_size_in_profile_info_dialog"
+            android:src="@drawable/add_a_photo_circled"
+            android:layout_gravity="bottom|right" />
+    </FrameLayout>
 
     <EditText
         android:id="@+id/user_name"
-        android:layout_width="0dp"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_gravity="bottom"
-        android:layout_weight="1"
+        android:layout_gravity="center"
         android:minWidth="200dp"
         android:layout_marginStart="6dp"
         android:minHeight="@dimen/min_tap_target_size"
diff --git a/packages/SettingsLib/res/values-w1280dp-land/dimens.xml b/packages/SettingsLib/res/values-w1280dp-land/dimens.xml
new file mode 100644
index 0000000..4097d05
--- /dev/null
+++ b/packages/SettingsLib/res/values-w1280dp-land/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+    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.
+  -->
+<resources>
+    <integer name="avatar_picker_columns">4</integer>
+    <dimen name="avatar_size_in_picker">104dp</dimen>
+    <dimen name="avatar_picker_padding">8dp</dimen>
+    <dimen name="avatar_picker_margin">3dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values-w1440dp-land/dimens.xml b/packages/SettingsLib/res/values-w1440dp-land/dimens.xml
new file mode 100644
index 0000000..764870e
--- /dev/null
+++ b/packages/SettingsLib/res/values-w1440dp-land/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+    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.
+  -->
+<resources>
+    <integer name="avatar_picker_columns">4</integer>
+    <dimen name="avatar_size_in_picker">128dp</dimen>
+    <dimen name="avatar_picker_padding">8dp</dimen>
+    <dimen name="avatar_picker_margin">4dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values-w1600dp-land/dimens.xml b/packages/SettingsLib/res/values-w1600dp-land/dimens.xml
new file mode 100644
index 0000000..872b88a
--- /dev/null
+++ b/packages/SettingsLib/res/values-w1600dp-land/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+    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.
+  -->
+<resources>
+    <integer name="avatar_picker_columns">4</integer>
+    <dimen name="avatar_size_in_picker">148dp</dimen>
+    <dimen name="avatar_picker_padding">8dp</dimen>
+    <dimen name="avatar_picker_margin">6dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values-w480dp-port/dimens.xml b/packages/SettingsLib/res/values-w480dp-port/dimens.xml
new file mode 100644
index 0000000..cab78d6
--- /dev/null
+++ b/packages/SettingsLib/res/values-w480dp-port/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+    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.
+  -->
+<resources>
+    <integer name="avatar_picker_columns">3</integer>
+    <dimen name="avatar_size_in_picker">112dp</dimen>
+    <dimen name="avatar_picker_padding">8dp</dimen>
+    <dimen name="avatar_picker_margin">3dp</dimen>
+</resources>
diff --git a/packages/SettingsLib/res/values-w600dp-port/dimens.xml b/packages/SettingsLib/res/values-w600dp-port/dimens.xml
new file mode 100644
index 0000000..4097d05
--- /dev/null
+++ b/packages/SettingsLib/res/values-w600dp-port/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+    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.
+  -->
+<resources>
+    <integer name="avatar_picker_columns">4</integer>
+    <dimen name="avatar_size_in_picker">104dp</dimen>
+    <dimen name="avatar_picker_padding">8dp</dimen>
+    <dimen name="avatar_picker_margin">3dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values-w720dp-port/dimens.xml b/packages/SettingsLib/res/values-w720dp-port/dimens.xml
new file mode 100644
index 0000000..764870e
--- /dev/null
+++ b/packages/SettingsLib/res/values-w720dp-port/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+    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.
+  -->
+<resources>
+    <integer name="avatar_picker_columns">4</integer>
+    <dimen name="avatar_size_in_picker">128dp</dimen>
+    <dimen name="avatar_picker_padding">8dp</dimen>
+    <dimen name="avatar_picker_margin">4dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values-w840dp-port/dimens.xml b/packages/SettingsLib/res/values-w840dp-port/dimens.xml
new file mode 100644
index 0000000..872b88a
--- /dev/null
+++ b/packages/SettingsLib/res/values-w840dp-port/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+    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.
+  -->
+<resources>
+    <integer name="avatar_picker_columns">4</integer>
+    <dimen name="avatar_size_in_picker">148dp</dimen>
+    <dimen name="avatar_picker_padding">8dp</dimen>
+    <dimen name="avatar_picker_margin">6dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values-w960dp-land/dimens.xml b/packages/SettingsLib/res/values-w960dp-land/dimens.xml
new file mode 100644
index 0000000..8403dba
--- /dev/null
+++ b/packages/SettingsLib/res/values-w960dp-land/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+    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.
+  -->
+<resources>
+    <integer name="avatar_picker_columns">4</integer>
+    <dimen name="avatar_size_in_picker">96dp</dimen>
+    <dimen name="avatar_picker_padding">6dp</dimen>
+    <dimen name="avatar_picker_margin">2dp</dimen>
+</resources>
diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml
index 2b5e9cd..93e3dee 100644
--- a/packages/SettingsLib/res/values/arrays.xml
+++ b/packages/SettingsLib/res/values/arrays.xml
@@ -647,4 +647,6 @@
         <item>disabled</item>
     </array>
 
+    <array name="avatar_images"/>
+
 </resources>
diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml
index 9bccc3f..120df76 100644
--- a/packages/SettingsLib/res/values/dimens.xml
+++ b/packages/SettingsLib/res/values/dimens.xml
@@ -98,4 +98,15 @@
     <!-- Minimum width for the popup for updating a user's photo. -->
     <dimen name="update_user_photo_popup_min_width">300dp</dimen>
 
+    <dimen name="add_a_photo_circled_padding">6dp</dimen>
+    <dimen name="user_photo_size_in_profile_info_dialog">112dp</dimen>
+    <dimen name="add_a_photo_icon_size_in_profile_info_dialog">32dp</dimen>
+
+    <integer name="avatar_picker_columns">3</integer>
+    <dimen name="avatar_size_in_picker">96dp</dimen>
+    <dimen name="avatar_picker_padding">6dp</dimen>
+    <dimen name="avatar_picker_margin">2dp</dimen>
+
+    <dimen name="avatar_picker_icon_inset">25dp</dimen>
+
 </resources>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index e4eab4b..45f8f1d 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1164,6 +1164,9 @@
     <!-- Summary for settings preference disabled by administrator [CHAR LIMIT=50] -->
     <string name="disabled_by_admin_summary_text">Controlled by admin</string>
 
+    <!-- Summary for settings preference disabled by app ops [CHAR LIMIT=50] -->
+    <string name="disabled_by_app_ops_text">Controlled by Restricted Setting</string>
+
     <!-- [CHAR LIMIT=25] Manage applications, text telling using an application is disabled. -->
     <string name="disabled">Disabled</string>
     <!-- Summary of app trusted to install apps [CHAR LIMIT=45] -->
@@ -1545,4 +1548,7 @@
 
     <!-- Content description of the no calling for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_no_calling">No calling.</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/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
index 1e8cb9f..1573edb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
@@ -26,14 +26,17 @@
 import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
+import android.os.Build;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.Settings;
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
 import android.text.style.ForegroundColorSpan;
@@ -42,6 +45,7 @@
 import android.view.MenuItem;
 import android.widget.TextView;
 
+import androidx.annotation.RequiresApi;
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.widget.LockPatternUtils;
@@ -729,6 +733,26 @@
     }
 
     /**
+     * Show restricted setting dialog.
+     */
+    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+    public static void sendShowRestrictedSettingDialogIntent(Context context,
+            String packageName, int uid) {
+        final Intent intent = getShowRestrictedSettingsIntent(packageName, uid);
+        context.startActivity(intent);
+    }
+
+    /**
+     * Get restricted settings dialog intent.
+     */
+    private static Intent getShowRestrictedSettingsIntent(String packageName, int uid) {
+        final Intent intent = new Intent(Settings.ACTION_SHOW_RESTRICTED_SETTING_DIALOG);
+        intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
+        intent.putExtra(Intent.EXTRA_UID, uid);
+        return intent;
+    }
+
+    /**
      * Static {@link LockPatternUtils} and {@link DevicePolicyManager} wrapper for testing purposes.
      * {@link LockPatternUtils} is an internal API not supported by robolectric.
      * {@link DevicePolicyManager} has a {@code getProfileParent} not yet suppored by robolectric.
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
index fc8b587..81146fa 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
@@ -19,6 +19,7 @@
 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
 
 import android.content.Context;
+import android.os.Process;
 import android.os.UserHandle;
 import android.util.AttributeSet;
 import android.view.View;
@@ -37,9 +38,14 @@
     RestrictedPreferenceHelper mHelper;
 
     public RestrictedPreference(Context context, AttributeSet attrs,
-            int defStyleAttr, int defStyleRes) {
+            int defStyleAttr, int defStyleRes, String packageName, int uid) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        mHelper = new RestrictedPreferenceHelper(context, this, attrs);
+        mHelper = new RestrictedPreferenceHelper(context, this, attrs, packageName, uid);
+    }
+
+    public RestrictedPreference(Context context, AttributeSet attrs,
+            int defStyleAttr, int defStyleRes) {
+        this(context, attrs, defStyleAttr, defStyleRes, null, Process.INVALID_UID);
     }
 
     public RestrictedPreference(Context context, AttributeSet attrs, int defStyleAttr) {
@@ -55,6 +61,11 @@
         this(context, null);
     }
 
+    public RestrictedPreference(Context context, String packageName, int uid) {
+        this(context, null, TypedArrayUtils.getAttr(context, R.attr.preferenceStyle,
+                android.R.attr.preferenceStyle), 0, packageName, uid);
+    }
+
     @Override
     protected int getSecondTargetResId() {
         return R.layout.restricted_icon;
@@ -115,7 +126,21 @@
         }
     }
 
+    public void setDisabledByAppOps(boolean disabled) {
+        if (mHelper.setDisabledByAppOps(disabled)) {
+            notifyChanged();
+        }
+    }
+
     public boolean isDisabledByAdmin() {
         return mHelper.isDisabledByAdmin();
     }
+
+    public int getUid() {
+        return mHelper != null ? mHelper.uid : Process.INVALID_UID;
+    }
+
+    public String getPackageName() {
+        return mHelper != null ? mHelper.packageName : null;
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
index 83a6973..3f322d6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
@@ -20,6 +20,7 @@
 
 import android.content.Context;
 import android.content.res.TypedArray;
+import android.os.Build;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.AttributeSet;
@@ -29,6 +30,8 @@
 import androidx.preference.Preference;
 import androidx.preference.PreferenceViewHolder;
 
+import com.android.internal.util.Preconditions;
+
 /**
  * Helper class for managing settings preferences that can be disabled
  * by device admins via user restrictions.
@@ -36,16 +39,22 @@
 public class RestrictedPreferenceHelper {
     private final Context mContext;
     private final Preference mPreference;
+    final String packageName;
+    final int uid;
 
     private boolean mDisabledByAdmin;
     private EnforcedAdmin mEnforcedAdmin;
     private String mAttrUserRestriction = null;
-    private boolean mUseAdminDisabledSummary = false;
+    private boolean mDisabledSummary = false;
+
+    private boolean mDisabledByAppOps;
 
     public RestrictedPreferenceHelper(Context context, Preference preference,
-            AttributeSet attrs) {
+            AttributeSet attrs, String packageName, int uid) {
         mContext = context;
         mPreference = preference;
+        this.packageName = packageName;
+        this.uid = uid;
 
         if (attrs != null) {
             final TypedArray attributes = context.obtainStyledAttributes(attrs,
@@ -71,27 +80,34 @@
             final TypedValue useAdminDisabledSummary =
                     attributes.peekValue(R.styleable.RestrictedPreference_useAdminDisabledSummary);
             if (useAdminDisabledSummary != null) {
-                mUseAdminDisabledSummary =
+                mDisabledSummary =
                         (useAdminDisabledSummary.type == TypedValue.TYPE_INT_BOOLEAN
                                 && useAdminDisabledSummary.data != 0);
             }
         }
     }
 
+    public RestrictedPreferenceHelper(Context context, Preference preference,
+            AttributeSet attrs) {
+        this(context, preference, attrs, null, android.os.Process.INVALID_UID);
+    }
+
     /**
      * Modify PreferenceViewHolder to add padlock if restriction is disabled.
      */
     public void onBindViewHolder(PreferenceViewHolder holder) {
-        if (mDisabledByAdmin) {
+        if (mDisabledByAdmin || mDisabledByAppOps) {
             holder.itemView.setEnabled(true);
         }
-        if (mUseAdminDisabledSummary) {
+        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);
                 if (mDisabledByAdmin) {
                     summaryView.setText(disabledText);
+                } else if (mDisabledByAppOps) {
+                    summaryView.setText(R.string.disabled_by_app_ops_text);
                 } else if (TextUtils.equals(disabledText, summaryView.getText())) {
                     // It's previously set to disabled text, clear it.
                     summaryView.setText(null);
@@ -101,7 +117,7 @@
     }
 
     public void useAdminDisabledSummary(boolean useSummary) {
-        mUseAdminDisabledSummary = useSummary;
+        mDisabledSummary = useSummary;
     }
 
     /**
@@ -109,11 +125,19 @@
      *
      * @return true if the method handled the click.
      */
+    @SuppressWarnings("NewApi")
     public boolean performClick() {
         if (mDisabledByAdmin) {
             RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mContext, mEnforcedAdmin);
             return true;
         }
+        if (mDisabledByAppOps) {
+            Preconditions.checkState(Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU,
+                    "Build SDK version needs >= T");
+            RestrictedLockUtilsInternal.sendShowRestrictedSettingDialogIntent(mContext, packageName,
+                    uid);
+            return true;
+        }
         return false;
     }
 
@@ -166,14 +190,34 @@
             changed = true;
         }
 
-        if (!(mPreference instanceof RestrictedTopLevelPreference)) {
-            mPreference.setEnabled(!disabled);
+        updateDisabledState();
+
+        return changed;
+    }
+
+    public boolean setDisabledByAppOps(boolean disabled) {
+        boolean changed = false;
+        if (mDisabledByAppOps != disabled) {
+            mDisabledByAppOps = disabled;
+            changed = true;
         }
 
+        updateDisabledState();
+
         return changed;
     }
 
     public boolean isDisabledByAdmin() {
         return mDisabledByAdmin;
     }
+
+    public boolean isDisabledByAppOps() {
+        return mDisabledByAppOps;
+    }
+
+    private void updateDisabledState() {
+        if (!(mPreference instanceof RestrictedTopLevelPreference)) {
+            mPreference.setEnabled(!(mDisabledByAdmin || mDisabledByAppOps));
+        }
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/AppIconCacheManager.java b/packages/SettingsLib/src/com/android/settingslib/applications/AppIconCacheManager.java
new file mode 100644
index 0000000..9dfc8ea
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/AppIconCacheManager.java
@@ -0,0 +1,112 @@
+/*
+ * 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.settingslib.applications;
+
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.LruCache;
+
+/**
+ * Cache app icon for management.
+ */
+public class AppIconCacheManager {
+    private static final String TAG = "AppIconCacheManager";
+    private static final float CACHE_RATIO = 0.1f;
+    private static final int MAX_CACHE_SIZE_IN_KB = getMaxCacheInKb();
+    private static final String DELIMITER = ":";
+    private static AppIconCacheManager sAppIconCacheManager;
+    private final LruCache<String, Drawable> mDrawableCache;
+
+    private AppIconCacheManager() {
+        mDrawableCache = new LruCache<String, Drawable>(MAX_CACHE_SIZE_IN_KB) {
+            @Override
+            protected int sizeOf(String key, Drawable drawable) {
+                if (drawable instanceof BitmapDrawable) {
+                    return ((BitmapDrawable) drawable).getBitmap().getByteCount() / 1024;
+                }
+                // Rough estimate each pixel will use 4 bytes by default.
+                return drawable.getIntrinsicHeight() * drawable.getIntrinsicWidth() * 4 / 1024;
+            }
+        };
+    }
+
+    /**
+     * Get an {@link AppIconCacheManager} instance.
+     */
+    public static synchronized AppIconCacheManager getInstance() {
+        if (sAppIconCacheManager == null) {
+            sAppIconCacheManager = new AppIconCacheManager();
+        }
+        return sAppIconCacheManager;
+    }
+
+    /**
+     * Put app icon to cache
+     *
+     * @param packageName of icon
+     * @param uid         of packageName
+     * @param drawable    app icon
+     */
+    public void put(String packageName, int uid, Drawable drawable) {
+        final String key = getKey(packageName, uid);
+        if (key == null || drawable == null || drawable.getIntrinsicHeight() < 0
+                || drawable.getIntrinsicWidth() < 0) {
+            Log.w(TAG, "Invalid key or drawable.");
+            return;
+        }
+        mDrawableCache.put(key, drawable);
+    }
+
+    /**
+     * Get app icon from cache.
+     *
+     * @param packageName of icon
+     * @param uid         of packageName
+     * @return app icon
+     */
+    public Drawable get(String packageName, int uid) {
+        final String key = getKey(packageName, uid);
+        if (key == null) {
+            Log.w(TAG, "Invalid key with package or uid.");
+            return null;
+        }
+        final Drawable cachedDrawable = mDrawableCache.get(key);
+        return cachedDrawable != null ? cachedDrawable.mutate() : null;
+    }
+
+    /**
+     * Release cache.
+     */
+    public static void release() {
+        if (sAppIconCacheManager != null) {
+            sAppIconCacheManager.mDrawableCache.evictAll();
+        }
+    }
+
+    private static String getKey(String packageName, int uid) {
+        if (packageName == null || uid < 0) {
+            return null;
+        }
+        return packageName + DELIMITER + UserHandle.getUserId(uid);
+    }
+
+    private static int getMaxCacheInKb() {
+        return Math.round(CACHE_RATIO * Runtime.getRuntime().maxMemory() / 1024);
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
index a5da8b6..cc4fef8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
@@ -25,6 +25,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Drawable;
 import android.hardware.usb.IUsbManager;
 import android.net.Uri;
 import android.os.Environment;
@@ -35,7 +36,9 @@
 import android.util.Log;
 
 import com.android.settingslib.R;
+import com.android.settingslib.Utils;
 import com.android.settingslib.applications.instantapps.InstantAppDataProvider;
+import com.android.settingslib.utils.ThreadUtils;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -212,4 +215,82 @@
                         UserHandle.myUserId());
         return TextUtils.equals(packageName, defaultBrowserPackage);
     }
+
+    /**
+     * Get the app icon by app entry.
+     *
+     * @param context  caller's context
+     * @param appEntry AppEntry of ApplicationsState
+     * @return app icon of the app entry
+     */
+    public static Drawable getIcon(Context context, ApplicationsState.AppEntry appEntry) {
+        if (appEntry == null || appEntry.info == null) {
+            return null;
+        }
+
+        final AppIconCacheManager appIconCacheManager = AppIconCacheManager.getInstance();
+        final String packageName = appEntry.info.packageName;
+        final int uid = appEntry.info.uid;
+
+        Drawable icon = appIconCacheManager.get(packageName, uid);
+        if (icon == null) {
+            if (appEntry.apkFile != null && appEntry.apkFile.exists()) {
+                icon = Utils.getBadgedIcon(context, appEntry.info);
+                appIconCacheManager.put(packageName, uid, icon);
+            } else {
+                setAppEntryMounted(appEntry, /* mounted= */ false);
+                icon = context.getDrawable(
+                        com.android.internal.R.drawable.sym_app_on_sd_unavailable_icon);
+            }
+        } else if (!appEntry.mounted && appEntry.apkFile != null && appEntry.apkFile.exists()) {
+            // If the app wasn't mounted but is now mounted, reload its icon.
+            setAppEntryMounted(appEntry, /* mounted= */ true);
+            icon = Utils.getBadgedIcon(context, appEntry.info);
+            appIconCacheManager.put(packageName, uid, icon);
+        }
+
+        return icon;
+    }
+
+    /**
+     * Get the app icon from cache by app entry.
+     *
+     * @param appEntry AppEntry of ApplicationsState
+     * @return app icon of the app entry
+     */
+    public static Drawable getIconFromCache(ApplicationsState.AppEntry appEntry) {
+        return appEntry == null || appEntry.info == null ? null
+                : AppIconCacheManager.getInstance().get(
+                        appEntry.info.packageName,
+                        appEntry.info.uid);
+    }
+
+    /**
+     * Preload the top N icons of app entry list.
+     *
+     * @param context caller's context
+     * @param appEntries AppEntry list of ApplicationsState
+     * @param number the number of Top N icons of the appEntries
+     */
+    public static void preloadTopIcons(Context context,
+            ArrayList<ApplicationsState.AppEntry> appEntries, int number) {
+        if (appEntries == null || appEntries.isEmpty() || number <= 0) {
+            return;
+        }
+
+        for (int i = 0; i < Math.min(appEntries.size(), number); i++) {
+            final ApplicationsState.AppEntry entry = appEntries.get(i);
+            ThreadUtils.postOnBackgroundThread(() -> {
+                getIcon(context, entry);
+            });
+        }
+    }
+
+    private static void setAppEntryMounted(ApplicationsState.AppEntry appEntry, boolean mounted) {
+        if (appEntry.mounted != mounted) {
+            synchronized (appEntry) {
+                appEntry.mounted = mounted;
+            }
+        }
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index f046f06..fdb0607 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -95,6 +95,7 @@
     private static final Object sLock = new Object();
     private static final Pattern REMOVE_DIACRITICALS_PATTERN
             = Pattern.compile("\\p{InCombiningDiacriticalMarks}+");
+    private static final String SETTING_PKG = "com.android.settings";
 
     @VisibleForTesting
     static ApplicationsState sInstance;
@@ -492,6 +493,9 @@
         return null;
     }
 
+    /**
+     * Starting Android T, this method will not be used if {@link AppIconCacheManager} is applied.
+     */
     public void ensureIcon(AppEntry entry) {
         if (entry.icon != null) {
             return;
@@ -758,6 +762,10 @@
         return null;
     }
 
+    private static boolean isAppIconCacheEnabled(Context context) {
+        return SETTING_PKG.equals(context.getPackageName());
+    }
+
     void rebuildActiveSessions() {
         synchronized (mEntriesMap) {
             if (!mSessionsChanged) {
@@ -806,6 +814,11 @@
             } else {
                 mHasLifecycle = false;
             }
+
+            if (isAppIconCacheEnabled(mContext)) {
+                // Skip the preloading all icons step to save memory usage.
+                mFlags = mFlags & ~FLAG_SESSION_REQUEST_ICONS;
+            }
         }
 
         @SessionFlags
@@ -814,7 +827,12 @@
         }
 
         public void setSessionFlags(@SessionFlags int flags) {
-            mFlags = flags;
+            if (isAppIconCacheEnabled(mContext)) {
+                // Skip the preloading all icons step to save memory usage.
+                mFlags = flags & ~FLAG_SESSION_REQUEST_ICONS;
+            } else {
+                mFlags = flags;
+            }
         }
 
         @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
@@ -1576,6 +1594,10 @@
 
         // Need to synchronize on 'this' for the following.
         public ApplicationInfo info;
+        /**
+         * Starting Android T, this field will not be used if {@link AppIconCacheManager} is
+         * applied.
+         */
         public Drawable icon;
         public String sizeStr;
         public String internalSizeStr;
@@ -1596,15 +1618,11 @@
             this.size = SIZE_UNKNOWN;
             this.sizeStale = true;
             ensureLabel(context);
-            // Speed up the cache of the icon and label description if they haven't been created.
-            ThreadUtils.postOnBackgroundThread(() -> {
-                if (this.icon == null) {
-                    this.ensureIconLocked(context);
-                }
-                if (this.labelDescription == null) {
-                    this.ensureLabelDescriptionLocked(context);
-                }
-            });
+            // Speed up the cache of the label description if they haven't been created.
+            if (this.labelDescription == null) {
+                ThreadUtils.postOnBackgroundThread(
+                        () -> this.ensureLabelDescriptionLocked(context));
+            }
         }
 
         public void ensureLabel(Context context) {
@@ -1620,7 +1638,15 @@
             }
         }
 
+        /**
+         * Starting Android T, this method will not be used if {@link AppIconCacheManager} is
+         * applied.
+         */
         boolean ensureIconLocked(Context context) {
+            if (isAppIconCacheEnabled(context)) {
+                return false;
+            }
+
             if (this.icon == null) {
                 if (this.apkFile.exists()) {
                     this.icon = Utils.getBadgedIcon(context, info);
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPhotoController.java b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPhotoController.java
new file mode 100644
index 0000000..61b8911
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPhotoController.java
@@ -0,0 +1,297 @@
+/*
+ * 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.settingslib.users;
+
+import android.app.Activity;
+import android.content.ClipData;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.media.ExifInterface;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.StrictMode;
+import android.provider.ContactsContract;
+import android.provider.MediaStore;
+import android.util.EventLog;
+import android.util.Log;
+
+import androidx.core.content.FileProvider;
+
+import libcore.io.Streams;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+class AvatarPhotoController {
+    private static final String TAG = "AvatarPhotoController";
+
+    private static final int REQUEST_CODE_CHOOSE_PHOTO = 1001;
+    private static final int REQUEST_CODE_TAKE_PHOTO = 1002;
+    private static final int REQUEST_CODE_CROP_PHOTO = 1003;
+    // in rare cases we get a null Cursor when querying for DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI
+    // so we need a default photo size
+    private static final int DEFAULT_PHOTO_SIZE = 500;
+
+    private static final String IMAGES_DIR = "multi_user";
+    private static final String CROP_PICTURE_FILE_NAME = "CropEditUserPhoto.jpg";
+    private static final String TAKE_PICTURE_FILE_NAME = "TakeEditUserPhoto.jpg";
+
+    private final int mPhotoSize;
+
+    private final AvatarPickerActivity mActivity;
+    private final String mFileAuthority;
+
+    private final File mImagesDir;
+    private final Uri mCropPictureUri;
+    private final Uri mTakePictureUri;
+
+    AvatarPhotoController(AvatarPickerActivity activity, boolean waiting, String fileAuthority) {
+        mActivity = activity;
+        mFileAuthority = fileAuthority;
+
+        mImagesDir = new File(activity.getCacheDir(), IMAGES_DIR);
+        mImagesDir.mkdir();
+        mCropPictureUri = createTempImageUri(activity, CROP_PICTURE_FILE_NAME, !waiting);
+        mTakePictureUri = createTempImageUri(activity, TAKE_PICTURE_FILE_NAME, !waiting);
+        mPhotoSize = getPhotoSize(activity);
+    }
+
+    /**
+     * Handles activity result from containing activity/fragment after a take/choose/crop photo
+     * action result is received.
+     */
+    public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (resultCode != Activity.RESULT_OK) {
+            return false;
+        }
+        final Uri pictureUri = data != null && data.getData() != null
+                ? data.getData() : mTakePictureUri;
+
+        // Check if the result is a content uri
+        if (!ContentResolver.SCHEME_CONTENT.equals(pictureUri.getScheme())) {
+            Log.e(TAG, "Invalid pictureUri scheme: " + pictureUri.getScheme());
+            EventLog.writeEvent(0x534e4554, "172939189", -1, pictureUri.getPath());
+            return false;
+        }
+
+        switch (requestCode) {
+            case REQUEST_CODE_CROP_PHOTO:
+                mActivity.returnUriResult(pictureUri);
+                return true;
+            case REQUEST_CODE_TAKE_PHOTO:
+            case REQUEST_CODE_CHOOSE_PHOTO:
+                if (mTakePictureUri.equals(pictureUri)) {
+                    if (PhotoCapabilityUtils.canCropPhoto(mActivity)) {
+                        cropPhoto();
+                    } else {
+                        onPhotoNotCropped(pictureUri);
+                    }
+                } else {
+                    copyAndCropPhoto(pictureUri);
+                }
+                return true;
+        }
+        return false;
+    }
+
+    void takePhoto() {
+        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE_SECURE);
+        appendOutputExtra(intent, mTakePictureUri);
+        mActivity.startActivityForResult(intent, REQUEST_CODE_TAKE_PHOTO);
+    }
+
+    void choosePhoto() {
+        Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES, null);
+        intent.setType("image/*");
+        mActivity.startActivityForResult(intent, REQUEST_CODE_CHOOSE_PHOTO);
+    }
+
+    private void copyAndCropPhoto(final Uri pictureUri) {
+        // TODO: Replace AsyncTask
+        new AsyncTask<Void, Void, Void>() {
+            @Override
+            protected Void doInBackground(Void... params) {
+                final ContentResolver cr = mActivity.getContentResolver();
+                try (InputStream in = cr.openInputStream(pictureUri);
+                     OutputStream out = cr.openOutputStream(mTakePictureUri)) {
+                    Streams.copy(in, out);
+                } catch (IOException e) {
+                    Log.w(TAG, "Failed to copy photo", e);
+                }
+                return null;
+            }
+
+            @Override
+            protected void onPostExecute(Void result) {
+                if (!mActivity.isFinishing() && !mActivity.isDestroyed()) {
+                    cropPhoto();
+                }
+            }
+        }.execute();
+    }
+
+    private void cropPhoto() {
+        // TODO: Use a public intent, when there is one.
+        Intent intent = new Intent("com.android.camera.action.CROP");
+        intent.setDataAndType(mTakePictureUri, "image/*");
+        appendOutputExtra(intent, mCropPictureUri);
+        appendCropExtras(intent);
+        if (intent.resolveActivity(mActivity.getPackageManager()) != null) {
+            try {
+                StrictMode.disableDeathOnFileUriExposure();
+                mActivity.startActivityForResult(intent, REQUEST_CODE_CROP_PHOTO);
+            } finally {
+                StrictMode.enableDeathOnFileUriExposure();
+            }
+        } else {
+            onPhotoNotCropped(mTakePictureUri);
+        }
+    }
+
+    private void appendOutputExtra(Intent intent, Uri pictureUri) {
+        intent.putExtra(MediaStore.EXTRA_OUTPUT, pictureUri);
+        intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+                | Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        intent.setClipData(ClipData.newRawUri(MediaStore.EXTRA_OUTPUT, pictureUri));
+    }
+
+    private void appendCropExtras(Intent intent) {
+        intent.putExtra("crop", "true");
+        intent.putExtra("scale", true);
+        intent.putExtra("scaleUpIfNeeded", true);
+        intent.putExtra("aspectX", 1);
+        intent.putExtra("aspectY", 1);
+        intent.putExtra("outputX", mPhotoSize);
+        intent.putExtra("outputY", mPhotoSize);
+    }
+
+    private void onPhotoNotCropped(final Uri data) {
+        // TODO: Replace AsyncTask to avoid possible memory leaks and handle configuration change
+        new AsyncTask<Void, Void, Bitmap>() {
+            @Override
+            protected Bitmap doInBackground(Void... params) {
+                // Scale and crop to a square aspect ratio
+                Bitmap croppedImage = Bitmap.createBitmap(mPhotoSize, mPhotoSize,
+                        Bitmap.Config.ARGB_8888);
+                Canvas canvas = new Canvas(croppedImage);
+                Bitmap fullImage;
+                try {
+                    InputStream imageStream = mActivity.getContentResolver()
+                            .openInputStream(data);
+                    fullImage = BitmapFactory.decodeStream(imageStream);
+                } catch (FileNotFoundException fe) {
+                    return null;
+                }
+                if (fullImage != null) {
+                    int rotation = getRotation(mActivity, data);
+                    final int squareSize = Math.min(fullImage.getWidth(),
+                            fullImage.getHeight());
+                    final int left = (fullImage.getWidth() - squareSize) / 2;
+                    final int top = (fullImage.getHeight() - squareSize) / 2;
+
+                    Matrix matrix = new Matrix();
+                    RectF rectSource = new RectF(left, top,
+                            left + squareSize, top + squareSize);
+                    RectF rectDest = new RectF(0, 0, mPhotoSize, mPhotoSize);
+                    matrix.setRectToRect(rectSource, rectDest, Matrix.ScaleToFit.CENTER);
+                    matrix.postRotate(rotation, mPhotoSize / 2f, mPhotoSize / 2f);
+                    canvas.drawBitmap(fullImage, matrix, new Paint());
+                    return croppedImage;
+                } else {
+                    // Bah! Got nothin.
+                    return null;
+                }
+            }
+
+            @Override
+            protected void onPostExecute(Bitmap bitmap) {
+                saveBitmapToFile(bitmap, new File(mImagesDir, CROP_PICTURE_FILE_NAME));
+                mActivity.returnUriResult(mCropPictureUri);
+            }
+        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+    }
+
+    /**
+     * Reads the image's exif data and determines the rotation degree needed to display the image
+     * in portrait mode.
+     */
+    private int getRotation(Context context, Uri selectedImage) {
+        int rotation = -1;
+        try {
+            InputStream imageStream = context.getContentResolver().openInputStream(selectedImage);
+            ExifInterface exif = new ExifInterface(imageStream);
+            rotation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1);
+        } catch (IOException exception) {
+            Log.e(TAG, "Error while getting rotation", exception);
+        }
+
+        switch (rotation) {
+            case ExifInterface.ORIENTATION_ROTATE_90:
+                return 90;
+            case ExifInterface.ORIENTATION_ROTATE_180:
+                return 180;
+            case ExifInterface.ORIENTATION_ROTATE_270:
+                return 270;
+            default:
+                return 0;
+        }
+    }
+
+    private void saveBitmapToFile(Bitmap bitmap, File file) {
+        try {
+            OutputStream os = new FileOutputStream(file);
+            bitmap.compress(Bitmap.CompressFormat.PNG, 100, os);
+            os.flush();
+            os.close();
+        } catch (IOException e) {
+            Log.e(TAG, "Cannot create temp file", e);
+        }
+    }
+
+    private static int getPhotoSize(Context context) {
+        try (Cursor cursor = context.getContentResolver().query(
+                ContactsContract.DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI,
+                new String[]{ContactsContract.DisplayPhoto.DISPLAY_MAX_DIM}, null, null, null)) {
+            if (cursor != null) {
+                cursor.moveToFirst();
+                return cursor.getInt(0);
+            } else {
+                return DEFAULT_PHOTO_SIZE;
+            }
+        }
+    }
+
+    private Uri createTempImageUri(Context context, String fileName, boolean purge) {
+        final File fullPath = new File(mImagesDir, fileName);
+        if (purge) {
+            fullPath.delete();
+        }
+        return FileProvider.getUriForFile(context, mFileAuthority, fullPath);
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java
new file mode 100644
index 0000000..50015e6
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java
@@ -0,0 +1,334 @@
+/*
+ * 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.settingslib.users;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import androidx.annotation.NonNull;
+import androidx.core.graphics.drawable.RoundedBitmapDrawable;
+import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.internal.util.UserIcons;
+import com.android.settingslib.R;
+
+import com.google.android.setupcompat.template.FooterBarMixin;
+import com.google.android.setupcompat.template.FooterButton;
+import com.google.android.setupdesign.GlifLayout;
+import com.google.android.setupdesign.util.ThemeHelper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Activity to allow the user to choose a user profile picture.
+ *
+ * <p>Options are provided to take a photo or choose a photo using the photo picker. In addition,
+ * preselected avatar images may be provided in the resource array {@code avatar_images}. If
+ * provided, every element of that array must be a bitmap drawable.
+ *
+ * <p>If preselected images are not provided, the default avatar will be shown instead, in a range
+ * of colors.
+ *
+ * <p>This activity should be started with startActivityForResult. If a photo or a preselected image
+ * is selected, a Uri will be returned in the data field of the result intent. If a colored default
+ * avatar is selected, the chosen color will be returned as {@code EXTRA_DEFAULT_ICON_TINT_COLOR}
+ * and the data field will be empty.
+ */
+public class AvatarPickerActivity extends Activity {
+
+    static final String EXTRA_FILE_AUTHORITY = "file_authority";
+    static final String EXTRA_DEFAULT_ICON_TINT_COLOR = "default_icon_tint_color";
+
+    private static final String KEY_AWAITING_RESULT = "awaiting_result";
+    private static final String KEY_SELECTED_POSITION = "selected_position";
+
+    private boolean mWaitingForActivityResult;
+
+    private FooterButton mDoneButton;
+    private AvatarAdapter mAdapter;
+
+    private AvatarPhotoController mAvatarPhotoController;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        ThemeHelper.trySetDynamicColor(this);
+        setContentView(R.layout.avatar_picker);
+        setUpButtons();
+
+        RecyclerView recyclerView = findViewById(R.id.avatar_grid);
+        mAdapter = new AvatarAdapter();
+        recyclerView.setAdapter(mAdapter);
+        recyclerView.setLayoutManager(new GridLayoutManager(this,
+                getResources().getInteger(R.integer.avatar_picker_columns)));
+
+        restoreState(savedInstanceState);
+
+        mAvatarPhotoController = new AvatarPhotoController(
+                this, mWaitingForActivityResult, getFileAuthority());
+    }
+
+    private void setUpButtons() {
+        GlifLayout glifLayout = findViewById(R.id.glif_layout);
+        FooterBarMixin mixin = glifLayout.getMixin(FooterBarMixin.class);
+
+        FooterButton secondaryButton =
+                new FooterButton.Builder(this)
+                        .setText("Cancel")
+                        .setListener(view -> cancel())
+                        .build();
+
+        mDoneButton =
+                new FooterButton.Builder(this)
+                        .setText("Done")
+                        .setListener(view -> mAdapter.returnSelectionResult())
+                        .build();
+        mDoneButton.setEnabled(false);
+
+        mixin.setSecondaryButton(secondaryButton);
+        mixin.setPrimaryButton(mDoneButton);
+    }
+
+    private String getFileAuthority() {
+        String authority = getIntent().getStringExtra(EXTRA_FILE_AUTHORITY);
+        if (authority == null) {
+            throw new IllegalStateException("File authority must be provided");
+        }
+        return authority;
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        mWaitingForActivityResult = false;
+        mAvatarPhotoController.onActivityResult(requestCode, resultCode, data);
+    }
+
+    @Override
+    protected void onSaveInstanceState(@NonNull Bundle outState) {
+        outState.putBoolean(KEY_AWAITING_RESULT, mWaitingForActivityResult);
+        outState.putInt(KEY_SELECTED_POSITION, mAdapter.mSelectedPosition);
+        super.onSaveInstanceState(outState);
+    }
+
+    private void restoreState(Bundle savedInstanceState) {
+        if (savedInstanceState != null) {
+            mWaitingForActivityResult = savedInstanceState.getBoolean(KEY_AWAITING_RESULT, false);
+            mAdapter.mSelectedPosition =
+                    savedInstanceState.getInt(KEY_SELECTED_POSITION, AvatarAdapter.NONE);
+        }
+    }
+
+    @Override
+    public void startActivityForResult(Intent intent, int requestCode) {
+        mWaitingForActivityResult = true;
+        super.startActivityForResult(intent, requestCode);
+    }
+
+    void returnUriResult(Uri uri) {
+        Intent resultData = new Intent();
+        resultData.setData(uri);
+        setResult(RESULT_OK, resultData);
+        finish();
+    }
+
+    void returnColorResult(int color) {
+        Intent resultData = new Intent();
+        resultData.putExtra(EXTRA_DEFAULT_ICON_TINT_COLOR, color);
+        setResult(RESULT_OK, resultData);
+        finish();
+    }
+
+    private void cancel() {
+        setResult(RESULT_CANCELED);
+        finish();
+    }
+
+    private class AvatarAdapter extends RecyclerView.Adapter<AvatarViewHolder> {
+
+        private static final int NONE = -1;
+
+        private final int mTakePhotoPosition;
+        private final int mChoosePhotoPosition;
+        private final int mPreselectedImageStartPosition;
+
+        private final List<Drawable> mImageDrawables;
+        private final TypedArray mPreselectedImages;
+        private final int[] mUserIconColors;
+        private int mSelectedPosition = NONE;
+
+        AvatarAdapter() {
+            final boolean canTakePhoto =
+                    PhotoCapabilityUtils.canTakePhoto(AvatarPickerActivity.this);
+            final boolean canChoosePhoto =
+                    PhotoCapabilityUtils.canChoosePhoto(AvatarPickerActivity.this);
+            mTakePhotoPosition = (canTakePhoto ? 0 : NONE);
+            mChoosePhotoPosition = (canChoosePhoto ? (canTakePhoto ? 1 : 0) : NONE);
+            mPreselectedImageStartPosition = (canTakePhoto ? 1 : 0) + (canChoosePhoto ? 1 : 0);
+
+            mPreselectedImages = getResources().obtainTypedArray(R.array.avatar_images);
+            mUserIconColors = UserIcons.getUserIconColors(getResources());
+            mImageDrawables = buildDrawableList();
+        }
+
+        @NonNull
+        @Override
+        public AvatarViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int position) {
+            LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
+            View itemView = layoutInflater.inflate(R.layout.avatar_item, parent, false);
+            return new AvatarViewHolder(itemView);
+        }
+
+        @Override
+        public void onBindViewHolder(@NonNull AvatarViewHolder viewHolder, int position) {
+            if (position == mTakePhotoPosition) {
+                viewHolder.setDrawable(getDrawable(R.drawable.avatar_take_photo_circled));
+                viewHolder.setClickListener(view -> mAvatarPhotoController.takePhoto());
+
+            } else if (position == mChoosePhotoPosition) {
+                viewHolder.setDrawable(getDrawable(R.drawable.avatar_choose_photo_circled));
+                viewHolder.setClickListener(view -> mAvatarPhotoController.choosePhoto());
+
+            } else if (position >= mPreselectedImageStartPosition) {
+                viewHolder.setSelected(position == mSelectedPosition);
+                viewHolder.setDrawable(mImageDrawables.get(indexFromPosition(position)));
+                viewHolder.setClickListener(view -> {
+                    if (mSelectedPosition == position) {
+                        deselect(position);
+                    } else {
+                        select(position);
+                    }
+                });
+            }
+        }
+
+        @Override
+        public int getItemCount() {
+            return mPreselectedImageStartPosition + mImageDrawables.size();
+        }
+
+        private List<Drawable> buildDrawableList() {
+            List<Drawable> result = new ArrayList<>();
+
+            for (int i = 0; i < mPreselectedImages.length(); i++) {
+                Drawable drawable = mPreselectedImages.getDrawable(i);
+                if (drawable instanceof BitmapDrawable) {
+                    result.add(circularDrawableFrom((BitmapDrawable) drawable));
+                } else {
+                    throw new IllegalStateException("Avatar drawables must be bitmaps");
+                }
+            }
+            if (!result.isEmpty()) {
+                return result;
+            }
+
+            // No preselected images. Use tinted default icon.
+            for (int i = 0; i < mUserIconColors.length; i++) {
+                result.add(UserIcons.getDefaultUserIconInColor(getResources(), mUserIconColors[i]));
+            }
+            return result;
+        }
+
+        private Drawable circularDrawableFrom(BitmapDrawable drawable) {
+            Bitmap bitmap = drawable.getBitmap();
+
+            RoundedBitmapDrawable roundedBitmapDrawable =
+                    RoundedBitmapDrawableFactory.create(getResources(), bitmap);
+            roundedBitmapDrawable.setCircular(true);
+
+            return roundedBitmapDrawable;
+        }
+
+        private int indexFromPosition(int position) {
+            return position - mPreselectedImageStartPosition;
+        }
+
+        private void select(int position) {
+            final int oldSelection = mSelectedPosition;
+            mSelectedPosition = position;
+            notifyItemChanged(position);
+            if (oldSelection != NONE) {
+                notifyItemChanged(oldSelection);
+            } else {
+                mDoneButton.setEnabled(true);
+            }
+        }
+
+        private void deselect(int position) {
+            mSelectedPosition = NONE;
+            notifyItemChanged(position);
+            mDoneButton.setEnabled(false);
+        }
+
+        private void returnSelectionResult() {
+            int index = indexFromPosition(mSelectedPosition);
+            if (mPreselectedImages.length() > 0) {
+                int resourceId = mPreselectedImages.getResourceId(index, -1);
+                if (resourceId == -1) {
+                    throw new IllegalStateException("Preselected avatar images must be resources.");
+                }
+                returnUriResult(uriForResourceId(resourceId));
+            } else {
+                returnColorResult(
+                        mUserIconColors[index]);
+            }
+        }
+
+        private Uri uriForResourceId(int resourceId) {
+            return new Uri.Builder()
+                    .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
+                    .authority(getResources().getResourcePackageName(resourceId))
+                    .appendPath(getResources().getResourceTypeName(resourceId))
+                    .appendPath(getResources().getResourceEntryName(resourceId))
+                    .build();
+        }
+    }
+
+    private static class AvatarViewHolder extends RecyclerView.ViewHolder {
+        private final ImageView mImageView;
+
+        AvatarViewHolder(View view) {
+            super(view);
+            mImageView = view.findViewById(R.id.avatar_image);
+        }
+
+        public void setDrawable(Drawable drawable) {
+            mImageView.setImageDrawable(drawable);
+        }
+
+        public void setClickListener(View.OnClickListener listener) {
+            mImageView.setOnClickListener(listener);
+        }
+
+        public void setSelected(boolean selected) {
+            mImageView.setSelected(selected);
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
index 5859953..80ee86f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
@@ -25,6 +25,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.WindowManager;
@@ -36,6 +37,8 @@
 
 import com.android.internal.util.UserIcons;
 import com.android.settingslib.R;
+import com.android.settingslib.RestrictedLockUtils;
+import com.android.settingslib.RestrictedLockUtilsInternal;
 import com.android.settingslib.drawable.CircleFramedDrawable;
 
 import java.io.File;
@@ -139,13 +142,20 @@
         Drawable userIcon = getUserIcon(activity, defaultUserIcon);
         userPhotoView.setImageDrawable(userIcon);
 
-        if (canChangePhoto(activity)) {
-            mEditUserPhotoController = createEditUserPhotoController(activity, activityStarter,
-                    userPhotoView);
+        if (isChangePhotoRestrictedByBase(activity)) {
+            // some users can't change their photos so we need to remove the suggestive icon
+            content.findViewById(R.id.add_a_photo_icon).setVisibility(View.GONE);
         } else {
-            // some users can't change their photos so we need to remove suggestive
-            // background from the photoView
-            userPhotoView.setBackground(null);
+            RestrictedLockUtils.EnforcedAdmin adminRestriction =
+                    getChangePhotoAdminRestriction(activity);
+            if (adminRestriction != null) {
+                userPhotoView.setOnClickListener(view ->
+                        RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
+                                activity, adminRestriction));
+            } else {
+                mEditUserPhotoController = createEditUserPhotoController(activity, activityStarter,
+                        userPhotoView);
+            }
         }
 
         mEditUserInfoDialog = buildDialog(activity, content, userNameView, oldUserIcon,
@@ -204,16 +214,21 @@
     }
 
     @VisibleForTesting
-    boolean canChangePhoto(Context context) {
-        return (PhotoCapabilityUtils.canCropPhoto(context)
-                && PhotoCapabilityUtils.canChoosePhoto(context))
-                || PhotoCapabilityUtils.canTakePhoto(context);
+    boolean isChangePhotoRestrictedByBase(Context context) {
+        return RestrictedLockUtilsInternal.hasBaseUserRestriction(
+                context, UserManager.DISALLOW_SET_USER_ICON, UserHandle.myUserId());
+    }
+
+    @VisibleForTesting
+    RestrictedLockUtils.EnforcedAdmin getChangePhotoAdminRestriction(Context context) {
+        return RestrictedLockUtilsInternal.checkIfRestrictionEnforced(
+                context, UserManager.DISALLOW_SET_USER_ICON, UserHandle.myUserId());
     }
 
     @VisibleForTesting
     EditUserPhotoController createEditUserPhotoController(Activity activity,
             ActivityStarter activityStarter, ImageView userPhotoView) {
         return new EditUserPhotoController(activity, activityStarter, userPhotoView,
-                mSavedPhoto, mWaitingForActivityResult, mFileAuthority);
+                mSavedPhoto, mFileAuthority);
     }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
index f9584a3..f8bb38b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
@@ -16,46 +16,21 @@
 
 package com.android.settingslib.users;
 
+import android.annotation.NonNull;
 import android.app.Activity;
-import android.content.ClipData;
-import android.content.ContentResolver;
-import android.content.Context;
 import android.content.Intent;
-import android.database.Cursor;
 import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
 import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
-import android.media.ExifInterface;
 import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.StrictMode;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.ContactsContract.DisplayPhoto;
-import android.provider.MediaStore;
-import android.util.EventLog;
 import android.util.Log;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
 import android.widget.ImageView;
-import android.widget.ListPopupWindow;
-import android.widget.TextView;
 
-import androidx.core.content.FileProvider;
-
+import com.android.internal.util.UserIcons;
 import com.android.settingslib.R;
-import com.android.settingslib.RestrictedLockUtils;
-import com.android.settingslib.RestrictedLockUtilsInternal;
 import com.android.settingslib.drawable.CircleFramedDrawable;
-
-import libcore.io.Streams;
+import com.android.settingslib.utils.ThreadUtils;
 
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -63,8 +38,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.concurrent.ExecutionException;
 
 /**
  * This class contains logic for starting activities to take/choose/crop photo, reads and transforms
@@ -75,45 +49,30 @@
 
     // It seems that this class generates custom request codes and they may
     // collide with ours, these values are very unlikely to have a conflict.
-    private static final int REQUEST_CODE_CHOOSE_PHOTO = 1001;
-    private static final int REQUEST_CODE_TAKE_PHOTO = 1002;
-    private static final int REQUEST_CODE_CROP_PHOTO = 1003;
-    // in rare cases we get a null Cursor when querying for DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI
-    // so we need a default photo size
-    private static final int DEFAULT_PHOTO_SIZE = 500;
+    private static final int REQUEST_CODE_PICK_AVATAR = 1004;
 
     private static final String IMAGES_DIR = "multi_user";
-    private static final String CROP_PICTURE_FILE_NAME = "CropEditUserPhoto.jpg";
-    private static final String TAKE_PICTURE_FILE_NAME = "TakeEditUserPhoto.jpg";
     private static final String NEW_USER_PHOTO_FILE_NAME = "NewUserPhoto.png";
 
-    private final int mPhotoSize;
-
     private final Activity mActivity;
     private final ActivityStarter mActivityStarter;
     private final ImageView mImageView;
     private final String mFileAuthority;
 
     private final File mImagesDir;
-    private final Uri mCropPictureUri;
-    private final Uri mTakePictureUri;
-
     private Bitmap mNewUserPhotoBitmap;
     private Drawable mNewUserPhotoDrawable;
 
     public EditUserPhotoController(Activity activity, ActivityStarter activityStarter,
-            ImageView view, Bitmap bitmap, boolean waiting, String fileAuthority) {
+            ImageView view, Bitmap bitmap, String fileAuthority) {
         mActivity = activity;
         mActivityStarter = activityStarter;
-        mImageView = view;
         mFileAuthority = fileAuthority;
 
         mImagesDir = new File(activity.getCacheDir(), IMAGES_DIR);
         mImagesDir.mkdir();
-        mCropPictureUri = createTempImageUri(activity, CROP_PICTURE_FILE_NAME, !waiting);
-        mTakePictureUri = createTempImageUri(activity, TAKE_PICTURE_FILE_NAME, !waiting);
-        mPhotoSize = getPhotoSize(activity);
-        mImageView.setOnClickListener(v -> showUpdatePhotoPopup());
+        mImageView = view;
+        mImageView.setOnClickListener(v -> showAvatarPicker());
         mNewUserPhotoBitmap = bitmap;
     }
 
@@ -125,32 +84,19 @@
         if (resultCode != Activity.RESULT_OK) {
             return false;
         }
-        final Uri pictureUri = data != null && data.getData() != null
-                ? data.getData() : mTakePictureUri;
 
-        // Check if the result is a content uri
-        if (!ContentResolver.SCHEME_CONTENT.equals(pictureUri.getScheme())) {
-            Log.e(TAG, "Invalid pictureUri scheme: " + pictureUri.getScheme());
-            EventLog.writeEvent(0x534e4554, "172939189", -1, pictureUri.getPath());
-            return false;
-        }
+        if (requestCode == REQUEST_CODE_PICK_AVATAR) {
+            if (data.hasExtra(AvatarPickerActivity.EXTRA_DEFAULT_ICON_TINT_COLOR)) {
+                int tintColor =
+                        data.getIntExtra(AvatarPickerActivity.EXTRA_DEFAULT_ICON_TINT_COLOR, -1);
+                onDefaultIconSelected(tintColor);
+                return true;
+            }
+            if (data.getData() != null) {
+                onPhotoCropped(data.getData());
+                return true;
+            }
 
-        switch (requestCode) {
-            case REQUEST_CODE_CROP_PHOTO:
-                onPhotoCropped(pictureUri);
-                return true;
-            case REQUEST_CODE_TAKE_PHOTO:
-            case REQUEST_CODE_CHOOSE_PHOTO:
-                if (mTakePictureUri.equals(pictureUri)) {
-                    if (PhotoCapabilityUtils.canCropPhoto(mActivity)) {
-                        cropPhoto();
-                    } else {
-                        onPhotoNotCropped(pictureUri);
-                    }
-                } else {
-                    copyAndCropPhoto(pictureUri);
-                }
-                return true;
         }
         return false;
     }
@@ -159,224 +105,60 @@
         return mNewUserPhotoDrawable;
     }
 
-    private void showUpdatePhotoPopup() {
-        final Context context = mImageView.getContext();
-        final boolean canTakePhoto = PhotoCapabilityUtils.canTakePhoto(context);
-        final boolean canChoosePhoto = PhotoCapabilityUtils.canChoosePhoto(context);
-
-        if (!canTakePhoto && !canChoosePhoto) {
-            return;
-        }
-
-        final List<EditUserPhotoController.RestrictedMenuItem> items = new ArrayList<>();
-
-        if (canTakePhoto) {
-            final String title = context.getString(R.string.user_image_take_photo);
-            items.add(new RestrictedMenuItem(context, title, UserManager.DISALLOW_SET_USER_ICON,
-                    this::takePhoto));
-        }
-
-        if (canChoosePhoto) {
-            final String title = context.getString(R.string.user_image_choose_photo);
-            items.add(new RestrictedMenuItem(context, title, UserManager.DISALLOW_SET_USER_ICON,
-                    this::choosePhoto));
-        }
-
-        final ListPopupWindow listPopupWindow = new ListPopupWindow(context);
-
-        listPopupWindow.setAnchorView(mImageView);
-        listPopupWindow.setModal(true);
-        listPopupWindow.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
-        listPopupWindow.setAdapter(new RestrictedPopupMenuAdapter(context, items));
-
-        final int width = Math.max(mImageView.getWidth(), context.getResources()
-                .getDimensionPixelSize(R.dimen.update_user_photo_popup_min_width));
-        listPopupWindow.setWidth(width);
-        listPopupWindow.setDropDownGravity(Gravity.START);
-
-        listPopupWindow.setOnItemClickListener((parent, view, position, id) -> {
-            listPopupWindow.dismiss();
-            final RestrictedMenuItem item =
-                    (RestrictedMenuItem) parent.getAdapter().getItem(position);
-            item.doAction();
-        });
-
-        listPopupWindow.show();
+    private void showAvatarPicker() {
+        Intent intent = new Intent(mImageView.getContext(), AvatarPickerActivity.class);
+        intent.putExtra(AvatarPickerActivity.EXTRA_FILE_AUTHORITY, mFileAuthority);
+        mActivityStarter.startActivityForResult(intent, REQUEST_CODE_PICK_AVATAR);
     }
 
-    private void takePhoto() {
-        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE_SECURE);
-        appendOutputExtra(intent, mTakePictureUri);
-        mActivityStarter.startActivityForResult(intent, REQUEST_CODE_TAKE_PHOTO);
-    }
+    private void onDefaultIconSelected(int tintColor) {
+        try {
+            ThreadUtils.postOnBackgroundThread(() -> {
+                Drawable drawable =
+                        UserIcons.getDefaultUserIconInColor(mActivity.getResources(), tintColor);
+                Bitmap bitmap = convertToBitmap(drawable,
+                        (int) mActivity.getResources().getDimension(R.dimen.circle_avatar_size));
 
-    private void choosePhoto() {
-        Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
-        intent.setType("image/*");
-        appendOutputExtra(intent, mTakePictureUri);
-        mActivityStarter.startActivityForResult(intent, REQUEST_CODE_CHOOSE_PHOTO);
-    }
-
-    private void copyAndCropPhoto(final Uri pictureUri) {
-        // TODO: Replace AsyncTask
-        new AsyncTask<Void, Void, Void>() {
-            @Override
-            protected Void doInBackground(Void... params) {
-                final ContentResolver cr = mActivity.getContentResolver();
-                try (InputStream in = cr.openInputStream(pictureUri);
-                     OutputStream out = cr.openOutputStream(mTakePictureUri)) {
-                    Streams.copy(in, out);
-                } catch (IOException e) {
-                    Log.w(TAG, "Failed to copy photo", e);
-                }
-                return null;
-            }
-
-            @Override
-            protected void onPostExecute(Void result) {
-                if (!mActivity.isFinishing() && !mActivity.isDestroyed()) {
-                    cropPhoto();
-                }
-            }
-        }.execute();
-    }
-
-    private void cropPhoto() {
-        // TODO: Use a public intent, when there is one.
-        Intent intent = new Intent("com.android.camera.action.CROP");
-        intent.setDataAndType(mTakePictureUri, "image/*");
-        appendOutputExtra(intent, mCropPictureUri);
-        appendCropExtras(intent);
-        if (intent.resolveActivity(mActivity.getPackageManager()) != null) {
-            try {
-                StrictMode.disableDeathOnFileUriExposure();
-                mActivityStarter.startActivityForResult(intent, REQUEST_CODE_CROP_PHOTO);
-            } finally {
-                StrictMode.enableDeathOnFileUriExposure();
-            }
-        } else {
-            onPhotoNotCropped(mTakePictureUri);
+                ThreadUtils.postOnMainThread(() -> onPhotoProcessed(bitmap));
+            }).get();
+        } catch (InterruptedException | ExecutionException e) {
+            Log.e(TAG, "Error processing default icon", e);
         }
     }
 
-    private void appendOutputExtra(Intent intent, Uri pictureUri) {
-        intent.putExtra(MediaStore.EXTRA_OUTPUT, pictureUri);
-        intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION
-                | Intent.FLAG_GRANT_READ_URI_PERMISSION);
-        intent.setClipData(ClipData.newRawUri(MediaStore.EXTRA_OUTPUT, pictureUri));
-    }
-
-    private void appendCropExtras(Intent intent) {
-        intent.putExtra("crop", "true");
-        intent.putExtra("scale", true);
-        intent.putExtra("scaleUpIfNeeded", true);
-        intent.putExtra("aspectX", 1);
-        intent.putExtra("aspectY", 1);
-        intent.putExtra("outputX", mPhotoSize);
-        intent.putExtra("outputY", mPhotoSize);
+    private static Bitmap convertToBitmap(@NonNull Drawable icon, int size) {
+        Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+        icon.setBounds(0, 0, size, size);
+        icon.draw(canvas);
+        return bitmap;
     }
 
     private void onPhotoCropped(final Uri data) {
-        // TODO: Replace AsyncTask to avoid possible memory leaks and handle configuration change
-        new AsyncTask<Void, Void, Bitmap>() {
-            @Override
-            protected Bitmap doInBackground(Void... params) {
-                InputStream imageStream = null;
-                try {
-                    imageStream = mActivity.getContentResolver()
-                            .openInputStream(data);
-                    return BitmapFactory.decodeStream(imageStream);
-                } catch (FileNotFoundException fe) {
-                    Log.w(TAG, "Cannot find image file", fe);
-                    return null;
-                } finally {
-                    if (imageStream != null) {
-                        try {
-                            imageStream.close();
-                        } catch (IOException ioe) {
-                            Log.w(TAG, "Cannot close image stream", ioe);
-                        }
+        ThreadUtils.postOnBackgroundThread(() -> {
+            InputStream imageStream = null;
+            Bitmap bitmap = null;
+            try {
+                imageStream = mActivity.getContentResolver()
+                        .openInputStream(data);
+                bitmap = BitmapFactory.decodeStream(imageStream);
+            } catch (FileNotFoundException fe) {
+                Log.w(TAG, "Cannot find image file", fe);
+            } finally {
+                if (imageStream != null) {
+                    try {
+                        imageStream.close();
+                    } catch (IOException ioe) {
+                        Log.w(TAG, "Cannot close image stream", ioe);
                     }
                 }
             }
 
-            @Override
-            protected void onPostExecute(Bitmap bitmap) {
-                onPhotoProcessed(bitmap);
-
+            if (bitmap != null) {
+                Bitmap finalBitmap = bitmap;
+                ThreadUtils.postOnMainThread(() -> onPhotoProcessed(finalBitmap));
             }
-        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
-    }
-
-    private void onPhotoNotCropped(final Uri data) {
-        // TODO: Replace AsyncTask to avoid possible memory leaks and handle configuration change
-        new AsyncTask<Void, Void, Bitmap>() {
-            @Override
-            protected Bitmap doInBackground(Void... params) {
-                // Scale and crop to a square aspect ratio
-                Bitmap croppedImage = Bitmap.createBitmap(mPhotoSize, mPhotoSize,
-                        Config.ARGB_8888);
-                Canvas canvas = new Canvas(croppedImage);
-                Bitmap fullImage;
-                try {
-                    InputStream imageStream = mActivity.getContentResolver()
-                            .openInputStream(data);
-                    fullImage = BitmapFactory.decodeStream(imageStream);
-                } catch (FileNotFoundException fe) {
-                    return null;
-                }
-                if (fullImage != null) {
-                    int rotation = getRotation(mActivity, data);
-                    final int squareSize = Math.min(fullImage.getWidth(),
-                            fullImage.getHeight());
-                    final int left = (fullImage.getWidth() - squareSize) / 2;
-                    final int top = (fullImage.getHeight() - squareSize) / 2;
-
-                    Matrix matrix = new Matrix();
-                    RectF rectSource = new RectF(left, top,
-                            left + squareSize, top + squareSize);
-                    RectF rectDest = new RectF(0, 0, mPhotoSize, mPhotoSize);
-                    matrix.setRectToRect(rectSource, rectDest, Matrix.ScaleToFit.CENTER);
-                    matrix.postRotate(rotation, mPhotoSize / 2f, mPhotoSize / 2f);
-                    canvas.drawBitmap(fullImage, matrix, new Paint());
-                    return croppedImage;
-                } else {
-                    // Bah! Got nothin.
-                    return null;
-                }
-            }
-
-            @Override
-            protected void onPostExecute(Bitmap bitmap) {
-                onPhotoProcessed(bitmap);
-            }
-        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
-    }
-
-    /**
-     * Reads the image's exif data and determines the rotation degree needed to display the image
-     * in portrait mode.
-     */
-    private int getRotation(Context context, Uri selectedImage) {
-        int rotation = -1;
-        try {
-            InputStream imageStream = context.getContentResolver().openInputStream(selectedImage);
-            ExifInterface exif = new ExifInterface(imageStream);
-            rotation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1);
-        } catch (IOException exception) {
-            Log.e(TAG, "Error while getting rotation", exception);
-        }
-
-        switch (rotation) {
-            case ExifInterface.ORIENTATION_ROTATE_90:
-                return 90;
-            case ExifInterface.ORIENTATION_ROTATE_180:
-                return 180;
-            case ExifInterface.ORIENTATION_ROTATE_270:
-                return 270;
-            default:
-                return 0;
-        }
+        });
     }
 
     private void onPhotoProcessed(Bitmap bitmap) {
@@ -386,29 +168,6 @@
                     .getInstance(mImageView.getContext(), mNewUserPhotoBitmap);
             mImageView.setImageDrawable(mNewUserPhotoDrawable);
         }
-        new File(mImagesDir, TAKE_PICTURE_FILE_NAME).delete();
-        new File(mImagesDir, CROP_PICTURE_FILE_NAME).delete();
-    }
-
-    private static int getPhotoSize(Context context) {
-        try (Cursor cursor = context.getContentResolver().query(
-                DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI,
-                new String[]{DisplayPhoto.DISPLAY_MAX_DIM}, null, null, null)) {
-            if (cursor != null) {
-                cursor.moveToFirst();
-                return cursor.getInt(0);
-            } else {
-                return DEFAULT_PHOTO_SIZE;
-            }
-        }
-    }
-
-    private Uri createTempImageUri(Context context, String fileName, boolean purge) {
-        final File fullPath = new File(mImagesDir, fileName);
-        if (purge) {
-            fullPath.delete();
-        }
-        return FileProvider.getUriForFile(context, mFileAuthority, fullPath);
     }
 
     File saveNewUserPhotoBitmap() {
@@ -435,84 +194,4 @@
     void removeNewUserPhotoBitmapFile() {
         new File(mImagesDir, NEW_USER_PHOTO_FILE_NAME).delete();
     }
-
-    private static final class RestrictedMenuItem {
-        private final Context mContext;
-        private final String mTitle;
-        private final Runnable mAction;
-        private final RestrictedLockUtils.EnforcedAdmin mAdmin;
-        // Restriction may be set by system or something else via UserManager.setUserRestriction().
-        private final boolean mIsRestrictedByBase;
-
-        /**
-         * The menu item, used for popup menu. Any element of such a menu can be disabled by admin.
-         *
-         * @param context     A context.
-         * @param title       The title of the menu item.
-         * @param restriction The restriction, that if is set, blocks the menu item.
-         * @param action      The action on menu item click.
-         */
-        RestrictedMenuItem(Context context, String title, String restriction,
-                Runnable action) {
-            mContext = context;
-            mTitle = title;
-            mAction = action;
-
-            final int myUserId = UserHandle.myUserId();
-            mAdmin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(context,
-                    restriction, myUserId);
-            mIsRestrictedByBase = RestrictedLockUtilsInternal.hasBaseUserRestriction(mContext,
-                    restriction, myUserId);
-        }
-
-        @Override
-        public String toString() {
-            return mTitle;
-        }
-
-        void doAction() {
-            if (isRestrictedByBase()) {
-                return;
-            }
-
-            if (isRestrictedByAdmin()) {
-                RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mContext, mAdmin);
-                return;
-            }
-
-            mAction.run();
-        }
-
-        boolean isRestrictedByAdmin() {
-            return mAdmin != null;
-        }
-
-        boolean isRestrictedByBase() {
-            return mIsRestrictedByBase;
-        }
-    }
-
-    /**
-     * Provide this adapter to ListPopupWindow.setAdapter() to have a popup window menu, where
-     * any element can be restricted by admin (profile owner or device owner).
-     */
-    private static final class RestrictedPopupMenuAdapter extends ArrayAdapter<RestrictedMenuItem> {
-        RestrictedPopupMenuAdapter(Context context, List<RestrictedMenuItem> items) {
-            super(context, R.layout.restricted_popup_menu_item, R.id.text, items);
-        }
-
-        @Override
-        public View getView(int position, View convertView, ViewGroup parent) {
-            final View view = super.getView(position, convertView, parent);
-            final RestrictedMenuItem item = getItem(position);
-            final TextView text = (TextView) view.findViewById(R.id.text);
-            final ImageView image = (ImageView) view.findViewById(R.id.restricted_icon);
-
-            text.setEnabled(!item.isRestrictedByAdmin() && !item.isRestrictedByBase());
-            image.setVisibility(item.isRestrictedByAdmin() && !item.isRestrictedByBase()
-                    ? ImageView.VISIBLE : ImageView.GONE);
-
-            return view;
-        }
-    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/PhotoCapabilityUtils.java b/packages/SettingsLib/src/com/android/settingslib/users/PhotoCapabilityUtils.java
index 165c280..b8615a7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/PhotoCapabilityUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/PhotoCapabilityUtils.java
@@ -40,12 +40,12 @@
 
     /**
      * Check if the current user can perform any activity for
-     * android.intent.action.GET_CONTENT action for images.
+     * ACTION_PICK_IMAGES action for images.
      * Returns false if the device is currently locked and
      * requires a PIN, pattern or password to unlock.
      */
     public static boolean canChoosePhoto(Context context) {
-        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+        Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
         intent.setType("image/*");
         boolean canPerformActivityForGetImage =
                 context.getPackageManager().queryIntentActivities(intent, 0).size() > 0;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppIconCacheManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppIconCacheManagerTest.java
new file mode 100644
index 0000000..64f8bef
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppIconCacheManagerTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.settingslib.applications;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+
+import android.graphics.drawable.Drawable;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class AppIconCacheManagerTest {
+
+    private static final String APP_PACKAGE_NAME = "com.test.app";
+    private static final int APP_UID = 9999;
+
+    @Mock
+    private Drawable mIcon;
+
+    private AppIconCacheManager mAppIconCacheManager;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mAppIconCacheManager = AppIconCacheManager.getInstance();
+        doReturn(10).when(mIcon).getIntrinsicHeight();
+        doReturn(10).when(mIcon).getIntrinsicWidth();
+        doReturn(mIcon).when(mIcon).mutate();
+    }
+
+    @After
+    public void tearDown() {
+        AppIconCacheManager.release();
+    }
+
+    @Test
+    public void get_invalidPackageOrUid_shouldReturnNull() {
+        assertThat(mAppIconCacheManager.get(/* packageName= */ null, /* uid= */ -1)).isNull();
+    }
+
+    @Test
+    public void put_invalidPackageOrUid_shouldNotCrash() {
+        mAppIconCacheManager.put(/* packageName= */ null, /* uid= */ 0, mIcon);
+        // no crash
+    }
+
+    @Test
+    public void put_invalidIcon_shouldNotCacheIcon() {
+        mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, /* drawable= */ null);
+
+        assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNull();
+    }
+
+    @Test
+    public void put_invalidIconSize_shouldNotCacheIcon() {
+        doReturn(-1).when(mIcon).getIntrinsicHeight();
+        doReturn(-1).when(mIcon).getIntrinsicWidth();
+
+        mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, mIcon);
+
+        assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNull();
+    }
+
+    @Test
+    public void put_shouldCacheIcon() {
+        mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, mIcon);
+
+        assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isEqualTo(mIcon);
+    }
+
+    @Test
+    public void release_noInstance_shouldNotCrash() {
+        mAppIconCacheManager = null;
+
+        AppIconCacheManager.release();
+        // no crash
+    }
+
+    @Test
+    public void release_existInstance_shouldClearCache() {
+        mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, mIcon);
+
+        AppIconCacheManager.release();
+
+        assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNull();
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java
new file mode 100644
index 0000000..8e448aa
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java
@@ -0,0 +1,167 @@
+/*
+ * 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.settingslib.applications;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.graphics.drawable.Drawable;
+
+import com.android.settingslib.Utils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(RobolectricTestRunner.class)
+public class AppUtilsTest {
+
+    private static final String APP_PACKAGE_NAME = "com.test.app";
+    private static final int APP_UID = 9999;
+
+    @Mock
+    private Drawable mIcon;
+
+    private Context mContext;
+    private AppIconCacheManager mAppIconCacheManager;
+    private ApplicationInfo mAppInfo;
+    private ApplicationsState.AppEntry mAppEntry;
+    private ArrayList<ApplicationsState.AppEntry> mAppEntries;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        mAppIconCacheManager = AppIconCacheManager.getInstance();
+        mAppInfo = createApplicationInfo(APP_PACKAGE_NAME, APP_UID);
+        mAppEntry = createAppEntry(mAppInfo, /* id= */ 1);
+        mAppEntries = new ArrayList<>(Arrays.asList(mAppEntry));
+        doReturn(mIcon).when(mIcon).mutate();
+    }
+
+    @After
+    public void tearDown() {
+        AppIconCacheManager.release();
+    }
+
+    @Test
+    public void getIcon_nullAppEntry_shouldReturnNull() {
+        assertThat(AppUtils.getIcon(mContext, /* appEntry= */ null)).isNull();
+    }
+
+    @Test
+    @Config(shadows = ShadowUtils.class)
+    public void getIcon_noCachedIcon_shouldNotReturnNull() {
+        assertThat(AppUtils.getIcon(mContext, mAppEntry)).isNotNull();
+    }
+
+    @Test
+    public void getIcon_existCachedIcon_shouldReturnCachedIcon() {
+        mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, mIcon);
+
+        assertThat(AppUtils.getIcon(mContext, mAppEntry)).isEqualTo(mIcon);
+    }
+
+    @Test
+    public void getIconFromCache_nullAppEntry_shouldReturnNull() {
+        assertThat(AppUtils.getIconFromCache(/* appEntry= */ null)).isNull();
+    }
+
+    @Test
+    public void getIconFromCache_shouldReturnCachedIcon() {
+        mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, mIcon);
+
+        assertThat(AppUtils.getIconFromCache(mAppEntry)).isEqualTo(mIcon);
+    }
+
+    @Test
+    public void preloadTopIcons_nullAppEntries_shouldNotCrash() {
+        AppUtils.preloadTopIcons(mContext, /* appEntries= */ null, /* number= */ 1);
+        // no crash
+    }
+
+    @Test
+    public void preloadTopIcons_zeroPreloadIcons_shouldNotCacheIcons() {
+        AppUtils.preloadTopIcons(mContext, mAppEntries, /* number= */ 0);
+
+        assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNull();
+    }
+
+    @Test
+    @Config(shadows = ShadowUtils.class)
+    public void preloadTopIcons_shouldCheckIconFromCache() throws InterruptedException {
+        AppUtils.preloadTopIcons(mContext, mAppEntries, /* number= */ 1);
+
+        TimeUnit.SECONDS.sleep(1);
+        assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNotNull();
+    }
+
+    private ApplicationsState.AppEntry createAppEntry(ApplicationInfo appInfo, int id) {
+        ApplicationsState.AppEntry appEntry = new ApplicationsState.AppEntry(mContext, appInfo, id);
+        appEntry.label = "label";
+        appEntry.mounted = true;
+        final File apkFile = mock(File.class);
+        doReturn(true).when(apkFile).exists();
+        try {
+            Field field = ApplicationsState.AppEntry.class.getDeclaredField("apkFile");
+            field.setAccessible(true);
+            field.set(appEntry, apkFile);
+        } catch (NoSuchFieldException | IllegalAccessException e) {
+            fail("Not able to mock apkFile: " + e);
+        }
+        return appEntry;
+    }
+
+    private ApplicationInfo createApplicationInfo(String packageName, int uid) {
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.sourceDir = "appPath";
+        appInfo.packageName = packageName;
+        appInfo.uid = uid;
+        return appInfo;
+    }
+
+    @Implements(Utils.class)
+    private static class ShadowUtils {
+        @Implementation
+        public static Drawable getBadgedIcon(Context context, ApplicationInfo appInfo) {
+            final Drawable icon = mock(Drawable.class);
+            doReturn(10).when(icon).getIntrinsicHeight();
+            doReturn(10).when(icon).getIntrinsicWidth();
+            doReturn(icon).when(icon).mutate();
+            return icon;
+        }
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
index 10ccd22..1f2297b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
@@ -33,6 +33,7 @@
 import static org.robolectric.shadow.api.Shadow.extract;
 
 import android.annotation.UserIdInt;
+import android.app.Application;
 import android.app.ApplicationPackageManager;
 import android.app.usage.StorageStats;
 import android.app.usage.StorageStatsManager;
@@ -110,6 +111,7 @@
     private ApplicationsState mApplicationsState;
     private Session mSession;
 
+    private Application mApplication;
 
     @Mock
     private Callbacks mCallbacks;
@@ -190,6 +192,7 @@
         ShadowContextImpl shadowContext = Shadow.extract(
                 RuntimeEnvironment.application.getBaseContext());
         shadowContext.setSystemService(Context.STORAGE_STATS_SERVICE, mStorageStatsManager);
+        mApplication = spy(RuntimeEnvironment.application);
         StorageStats storageStats = new StorageStats();
         storageStats.codeBytes = 10;
         storageStats.cacheBytes = 30;
@@ -207,8 +210,7 @@
             anyLong() /* flags */, anyInt() /* userId */)).thenReturn(new ParceledListSlice(infos));
 
         ApplicationsState.sInstance = null;
-        mApplicationsState =
-            ApplicationsState.getInstance(RuntimeEnvironment.application, mPackageManagerService);
+        mApplicationsState = ApplicationsState.getInstance(mApplication, mPackageManagerService);
         mApplicationsState.clearEntries();
 
         mSession = mApplicationsState.newSession(mCallbacks);
@@ -703,6 +705,23 @@
         verify(mApplicationsState, never()).clearEntries();
     }
 
+    @Test
+    public void testDefaultSession_enabledAppIconCache_shouldSkipPreloadIcon() {
+        when(mApplication.getPackageName()).thenReturn("com.android.settings");
+        mSession.onResume();
+
+        addApp(HOME_PACKAGE_NAME, 1);
+        addApp(LAUNCHABLE_PACKAGE_NAME, 2);
+        mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
+        processAllMessages();
+        verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture());
+
+        List<AppEntry> appEntries = mAppEntriesCaptor.getValue();
+        for (AppEntry appEntry : appEntries) {
+            assertThat(appEntry.icon).isNull();
+        }
+    }
+
     private void setupDoResumeIfNeededLocked(ArrayList<ApplicationInfo> ownerApps,
             ArrayList<ApplicationInfo> profileApps)
             throws RemoteException {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ConnectivityPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ConnectivityPreferenceControllerTest.java
index c1cc3ae..445701f 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ConnectivityPreferenceControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ConnectivityPreferenceControllerTest.java
@@ -18,6 +18,7 @@
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.doReturn;
@@ -33,7 +34,6 @@
 import com.android.settingslib.core.lifecycle.Lifecycle;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -55,7 +55,6 @@
     }
 
     @Test
-    @Ignore
     public void testBroadcastReceiver() {
         final AbstractConnectivityPreferenceController preferenceController =
                 spy(new ConcreteConnectivityPreferenceController(mContext, mLifecycle));
@@ -73,7 +72,7 @@
         verify(mContext, times(1))
                 .registerReceiver(receiverArgumentCaptor.capture(),
                         filterArgumentCaptor.capture(),
-                        anyString(), nullable(Handler.class));
+                        anyString(), nullable(Handler.class), anyInt());
 
         final BroadcastReceiver receiver = receiverArgumentCaptor.getValue();
         final IntentFilter filter = filterArgumentCaptor.getValue();
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java
index d6c8816..a5ee4c3 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java
@@ -62,7 +62,7 @@
     @Mock
     private ActivityStarter mActivityStarter;
 
-    private boolean mCanChangePhoto;
+    private boolean mPhotoRestrictedByBase;
     private Activity mActivity;
     private TestEditUserInfoController mController;
 
@@ -85,8 +85,8 @@
         }
 
         @Override
-        boolean canChangePhoto(Context context) {
-            return mCanChangePhoto;
+        boolean isChangePhotoRestrictedByBase(Context context) {
+            return mPhotoRestrictedByBase;
         }
     }
 
@@ -96,7 +96,7 @@
         mActivity = spy(ActivityController.of(new FragmentActivity()).get());
         mActivity.setTheme(R.style.Theme_AppCompat_DayNight);
         mController = new TestEditUserInfoController();
-        mCanChangePhoto = true;
+        mPhotoRestrictedByBase = true;
     }
 
     @Test
@@ -260,7 +260,7 @@
 
     @Test
     public void createDialog_canNotChangePhoto_nullPhotoController() {
-        mCanChangePhoto = false;
+        mPhotoRestrictedByBase = false;
 
         mController.createDialog(mActivity, mActivityStarter, mCurrentIcon,
                 "test", "title", null, null);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
index 10ac829..3b18c57 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
@@ -64,6 +64,20 @@
     }
 
     @Test
+    public void setLearnMoreText_shouldSetAsTextInLearnMore() {
+        final PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(
+                LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null));
+        mFooterPreference.setLearnMoreText("Custom learn more");
+        mFooterPreference.setLearnMoreAction(view -> { /* do nothing */ } /* listener */);
+
+        mFooterPreference.onBindViewHolder(holder);
+
+        assertThat(((TextView) holder.findViewById(
+                        R.id.settingslib_learn_more)).getText().toString())
+                .isEqualTo("Custom learn more");
+    }
+
+    @Test
     public void setContentDescription_contentSet_shouldGetSameContentDescription() {
         mFooterPreference.setContentDescription("test");
 
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 52a708d..13ae870 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -326,6 +326,8 @@
                     Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL_MAX,
                     Settings.Global.LOW_POWER_MODE_STICKY,
                     Settings.Global.LOW_POWER_MODE_SUGGESTION_PARAMS,
+                    Settings.Global.LOW_POWER_STANDBY_ACTIVE_DURING_MAINTENANCE,
+                    Settings.Global.LOW_POWER_STANDBY_ENABLED,
                     Settings.Global.LTE_SERVICE_FORCED,
                     Settings.Global.LID_BEHAVIOR,
                     Settings.Global.MAX_ERROR_BYTES_PREFIX,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index ebff00c..27fc6ba 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -248,6 +248,7 @@
     <uses-permission android:name="android.permission.MANAGE_CONTENT_CAPTURE" />
     <uses-permission android:name="android.permission.MANAGE_CONTENT_SUGGESTIONS" />
     <uses-permission android:name="android.permission.MANAGE_APP_PREDICTIONS" />
+    <uses-permission android:name="android.permission.MANAGE_LOW_POWER_STANDBY" />
     <uses-permission android:name="android.permission.MANAGE_SEARCH_UI" />
     <uses-permission android:name="android.permission.MANAGE_SMARTSPACE" />
     <uses-permission android:name="android.permission.MANAGE_WALLPAPER_EFFECTS_GENERATION" />
diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
index 68c8c3e..ffee894 100644
--- a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
+++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
@@ -20,6 +20,7 @@
 import android.app.smartspace.SmartspaceAction;
 import android.app.smartspace.SmartspaceTarget;
 import android.app.smartspace.SmartspaceTargetEvent;
+import android.app.smartspace.uitemplatedata.SmartspaceTapAction;
 import android.content.ActivityNotFoundException;
 import android.content.Intent;
 import android.graphics.drawable.Drawable;
@@ -143,6 +144,18 @@
             }
         }
 
+        default void startFromAction(SmartspaceTapAction action, View v, boolean showOnLockscreen) {
+            try {
+                if (action.getIntent() != null) {
+                    startIntent(v, action.getIntent(), showOnLockscreen);
+                } else if (action.getPendingIntent() != null) {
+                    startPendingIntent(action.getPendingIntent(), showOnLockscreen);
+                }
+            } catch (ActivityNotFoundException e) {
+                Log.w(TAG, "Could not launch intent for action: " + action, e);
+            }
+        }
+
         /** Start the intent */
         void startIntent(View v, Intent i, boolean showOnLockscreen);
 
diff --git a/packages/SystemUI/res/layout/controls_fullscreen.xml b/packages/SystemUI/res/layout/controls_fullscreen.xml
index 7fd029c..11a5665 100644
--- a/packages/SystemUI/res/layout/controls_fullscreen.xml
+++ b/packages/SystemUI/res/layout/controls_fullscreen.xml
@@ -35,7 +35,8 @@
         android:layout_height="wrap_content"
         android:clipChildren="false"
         android:orientation="vertical"
-        android:clipToPadding="false" />
+        android:clipToPadding="false"
+        android:paddingHorizontal="@dimen/controls_padding_horizontal" />
 
   </com.android.systemui.globalactions.MinHeightScrollView>
 </LinearLayout>
diff --git a/packages/SystemUI/res/layout/dream_overlay_complications_layer.xml b/packages/SystemUI/res/layout/dream_overlay_complications_layer.xml
index 5135947..aaff3f9 100644
--- a/packages/SystemUI/res/layout/dream_overlay_complications_layer.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_complications_layer.xml
@@ -20,6 +20,30 @@
     android:id="@+id/dream_overlay_complications_layer"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
+    <androidx.constraintlayout.widget.Guideline
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/complication_top_guide"
+        app:layout_constraintGuide_percent="@dimen/dream_overlay_complication_guide_top_percent"
+        android:orientation="horizontal"/>
+    <androidx.constraintlayout.widget.Guideline
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/complication_end_guide"
+        app:layout_constraintGuide_percent="@dimen/dream_overlay_complication_guide_end_percent"
+        android:orientation="vertical"/>
+    <androidx.constraintlayout.widget.Guideline
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/complication_bottom_guide"
+        app:layout_constraintGuide_percent="@dimen/dream_overlay_complication_guide_bottom_percent"
+        android:orientation="horizontal"/>
+    <androidx.constraintlayout.widget.Guideline
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/complication_start_guide"
+        app:layout_constraintGuide_percent="@dimen/dream_overlay_complication_guide_start_percent"
+        android:orientation="vertical"/>
     <TextClock
         android:id="@+id/time_view"
         android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/values-land/config.xml b/packages/SystemUI/res/values-land/config.xml
index ac4dfd2..8c5006d 100644
--- a/packages/SystemUI/res/values-land/config.xml
+++ b/packages/SystemUI/res/values-land/config.xml
@@ -31,9 +31,6 @@
     <!-- orientation of the dead zone when touches have recently occurred elsewhere on screen -->
     <integer name="navigation_bar_deadzone_orientation">1</integer>
 
-    <!-- Max number of columns for quick controls area -->
-    <integer name="controls_max_columns">4</integer>
-
     <!-- Max number of columns for power menu -->
     <integer name="power_menu_max_columns">4</integer>
 </resources>
diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml
index dabc310..fe546f6 100644
--- a/packages/SystemUI/res/values-sw600dp-land/config.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/config.xml
@@ -15,9 +15,6 @@
   ~ limitations under the License
   -->
 <resources>
-    <!-- Max number of columns for quick controls area -->
-    <integer name="controls_max_columns">2</integer>
-
     <!-- The maximum number of rows in the QSPanel -->
     <integer name="quick_settings_max_rows">3</integer>
 
diff --git a/packages/SystemUI/res/values-sw600dp-port/config.xml b/packages/SystemUI/res/values-sw600dp-port/config.xml
index 02fd25b..3c6a81e 100644
--- a/packages/SystemUI/res/values-sw600dp-port/config.xml
+++ b/packages/SystemUI/res/values-sw600dp-port/config.xml
@@ -15,7 +15,6 @@
   ~ limitations under the License
   -->
 <resources>
-
     <!-- The maximum number of tiles in the QuickQSPanel -->
     <integer name="quick_qs_panel_max_tiles">6</integer>
 
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index f5dc7e3e..1b8453a 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -29,9 +29,6 @@
     <!-- orientation of the dead zone when touches have recently occurred elsewhere on screen -->
     <integer name="navigation_bar_deadzone_orientation">0</integer>
 
-    <!-- Max number of columns for quick controls area -->
-    <integer name="controls_max_columns">4</integer>
-
     <!-- How many lines to show in the security footer -->
     <integer name="qs_security_footer_maxLines">1</integer>
 
diff --git a/packages/SystemUI/res/values-sw720dp-land/config.xml b/packages/SystemUI/res/values-sw720dp-land/config.xml
index ae89ef4..be34a48 100644
--- a/packages/SystemUI/res/values-sw720dp-land/config.xml
+++ b/packages/SystemUI/res/values-sw720dp-land/config.xml
@@ -15,9 +15,6 @@
   ~ limitations under the License
   -->
 <resources>
-    <!-- Max number of columns for quick controls area -->
-    <integer name="controls_max_columns">2</integer>
-
     <!-- The maximum number of rows in the QSPanel -->
     <integer name="quick_settings_max_rows">3</integer>
 
diff --git a/packages/SystemUI/res/values-w500dp/config.xml b/packages/SystemUI/res/values-w500dp/config.xml
new file mode 100644
index 0000000..ef499ff
--- /dev/null
+++ b/packages/SystemUI/res/values-w500dp/config.xml
@@ -0,0 +1,20 @@
+<!--
+  ~ 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.
+  -->
+
+<resources>
+    <!-- Max number of columns for quick controls area -->
+    <integer name="controls_max_columns">3</integer>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-w500dp/dimens.xml b/packages/SystemUI/res/values-w500dp/dimens.xml
new file mode 100644
index 0000000..5ce5cee
--- /dev/null
+++ b/packages/SystemUI/res/values-w500dp/dimens.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ 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.
+  -->
+
+<resources>
+    <dimen name="controls_padding_horizontal">75dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/values-w850dp/config.xml b/packages/SystemUI/res/values-w850dp/config.xml
new file mode 100644
index 0000000..337ebe1
--- /dev/null
+++ b/packages/SystemUI/res/values-w850dp/config.xml
@@ -0,0 +1,20 @@
+<!--
+  ~ 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.
+  -->
+
+<resources>
+    <!-- Max number of columns for quick controls area -->
+    <integer name="controls_max_columns">4</integer>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-w850dp/dimens.xml b/packages/SystemUI/res/values-w850dp/dimens.xml
new file mode 100644
index 0000000..bb6ba8fb
--- /dev/null
+++ b/packages/SystemUI/res/values-w850dp/dimens.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ 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.
+  -->
+
+<resources>
+    <dimen name="controls_padding_horizontal">205dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index f94031c..800dd0a 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -369,6 +369,13 @@
     <!-- The top margin of the panel that holds the list of notifications. -->
     <dimen name="notification_panel_margin_top">0dp</dimen>
 
+    <!-- The minimum content height for the split shade NSSL.
+         It is used because if the height is too small, the expansion motion is too fast.
+         Note that the value of 256dp is more or less a random value and can be changed to tweak
+         the expansion motion.
+    -->
+    <dimen name="nssl_split_shade_min_content_height">256dp</dimen>
+
     <!-- The bottom margin of the panel that holds the list of notifications. -->
     <dimen name="notification_panel_margin_bottom">0dp</dimen>
 
@@ -1028,6 +1035,7 @@
     <dimen name="controls_header_bottom_margin">24dp</dimen>
     <dimen name="controls_header_app_icon_size">24dp</dimen>
     <dimen name="controls_top_margin">48dp</dimen>
+    <dimen name="controls_padding_horizontal">0dp</dimen>
     <dimen name="control_header_text_size">20sp</dimen>
     <dimen name="control_item_text_size">16sp</dimen>
     <dimen name="control_menu_item_text_size">16sp</dimen>
@@ -1336,4 +1344,37 @@
     <!-- Height of the area at the top of the dream overlay to allow dragging down the notifications
          shade. -->
     <dimen name="dream_overlay_notifications_drag_area_height">100dp</dimen>
+
+    <!-- The position of the end guide, which dream overlay complications can align their start with
+         if their end is aligned with the parent end. Represented as the percentage over from the
+         start of the parent container. -->
+    <item name="dream_overlay_complication_guide_end_percent" format="float" type="dimen">
+        0.75
+    </item>
+
+    <!-- The position of the start guide, which dream overlay complications can align their end to
+         if their start is aligned with the parent start. Represented as the percentage over from
+         the start of the parent container. -->
+    <item name="dream_overlay_complication_guide_start_percent" format="float" type="dimen">
+        0.25
+    </item>
+
+    <!-- The position of the bottom guide, which dream overlay complications can align their top to
+         if their bottom is aligned with the parent bottom. Represented as the percentage over from
+         the top of the parent container. -->
+    <item name="dream_overlay_complication_guide_bottom_percent" format="float" type="dimen">
+        0.90
+    </item>
+
+    <!-- The position of the top guide, which dream overlay complications can align their bottom to
+     if their top is aligned with the parent top. Represented as the percentage over from
+     the top of the parent container. -->
+    <item name="dream_overlay_complication_guide_top_percent" format="float" type="dimen">
+        0.10
+    </item>
+
+    <!-- The percentage of the screen from which a swipe can start to reveal the bouncer. -->
+    <item name="dream_overlay_bouncer_start_region_screen_percentage" format="float" type="dimen">
+        .2
+    </item>
 </resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 5d092d0..eebc791 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -120,6 +120,8 @@
     public static final int SYSUI_STATE_BACK_DISABLED = 1 << 22;
     // The bubble stack is expanded AND the mange menu for bubbles is expanded on top of it.
     public static final int SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED = 1 << 23;
+    // The current app is in immersive mode
+    public static final int SYSUI_STATE_IMMERSIVE_MODE = 1 << 24;
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({SYSUI_STATE_SCREEN_PINNING,
@@ -145,7 +147,8 @@
             SYSUI_STATE_IME_SWITCHER_SHOWING,
             SYSUI_STATE_DEVICE_DOZING,
             SYSUI_STATE_BACK_DISABLED,
-            SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED
+            SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED,
+            SYSUI_STATE_IMMERSIVE_MODE
     })
     public @interface SystemUiStateFlags {}
 
@@ -179,6 +182,7 @@
         str.add((flags & SYSUI_STATE_BACK_DISABLED) != 0 ? "back_disabled" : "");
         str.add((flags & SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0
                 ? "bubbles_mange_menu_expanded" : "");
+        str.add((flags & SYSUI_STATE_IMMERSIVE_MODE) != 0 ? "immersive_mode" : "");
         return str.toString();
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt
index 214b284..43cd764 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt
@@ -16,6 +16,7 @@
 
 package com.android.keyguard
 
+import android.app.StatusBarManager.SESSION_KEYGUARD
 import android.content.Context
 import android.hardware.biometrics.BiometricSourceType
 import com.android.internal.annotations.VisibleForTesting
@@ -28,7 +29,7 @@
 import com.android.keyguard.KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.SessionTracker
 import java.io.FileDescriptor
 import java.io.PrintWriter
 import javax.inject.Inject
@@ -44,7 +45,7 @@
     context: Context?,
     private val uiEventLogger: UiEventLogger,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
-    private val dumpManager: DumpManager
+    private val sessionTracker: SessionTracker
 ) : CoreStartable(context) {
     private var fingerprintLockedOut = false
     private var faceLockedOut = false
@@ -53,7 +54,6 @@
     private var timeout = false
 
     override fun start() {
-        dumpManager.registerDumpable(this)
         mKeyguardUpdateMonitorCallback.onStrongAuthStateChanged(
                 KeyguardUpdateMonitor.getCurrentUser())
         keyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback)
@@ -65,22 +65,17 @@
             if (biometricSourceType == BiometricSourceType.FINGERPRINT) {
                 val lockedOut = keyguardUpdateMonitor.isFingerprintLockedOut
                 if (lockedOut && !fingerprintLockedOut) {
-                    uiEventLogger.log(
-                            PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT)
+                    log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT)
                 } else if (!lockedOut && fingerprintLockedOut) {
-                    uiEventLogger.log(
-                            PrimaryAuthRequiredEvent
-                                    .PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT_RESET)
+                    log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT_RESET)
                 }
                 fingerprintLockedOut = lockedOut
             } else if (biometricSourceType == BiometricSourceType.FACE) {
                 val lockedOut = keyguardUpdateMonitor.isFaceLockedOut
                 if (lockedOut && !faceLockedOut) {
-                    uiEventLogger.log(
-                            PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT)
+                    log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT)
                 } else if (!lockedOut && faceLockedOut) {
-                    uiEventLogger.log(
-                            PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT_RESET)
+                    log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT_RESET)
                 }
                 faceLockedOut = lockedOut
             }
@@ -95,20 +90,19 @@
 
             val newEncryptedOrLockdown = keyguardUpdateMonitor.isEncryptedOrLockdown(userId)
             if (newEncryptedOrLockdown && !encryptedOrLockdown) {
-                uiEventLogger.log(
-                        PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_ENCRYPTED_OR_LOCKDOWN)
+                log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_ENCRYPTED_OR_LOCKDOWN)
             }
             encryptedOrLockdown = newEncryptedOrLockdown
 
             val newUnattendedUpdate = isUnattendedUpdate(strongAuthFlags)
             if (newUnattendedUpdate && !unattendedUpdate) {
-                uiEventLogger.log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE)
+                log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE)
             }
             unattendedUpdate = newUnattendedUpdate
 
             val newTimeout = isStrongAuthTimeout(strongAuthFlags)
             if (newTimeout && !timeout) {
-                uiEventLogger.log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_TIMEOUT)
+                log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_TIMEOUT)
             }
             timeout = newTimeout
         }
@@ -123,6 +117,9 @@
     ) = containsFlag(flags, STRONG_AUTH_REQUIRED_AFTER_TIMEOUT) ||
             containsFlag(flags, STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT)
 
+    private fun log(event: PrimaryAuthRequiredEvent) =
+            uiEventLogger.log(event, sessionTracker.getSessionId(SESSION_KEYGUARD))
+
     override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
         pw.println("  mFingerprintLockedOut=$fingerprintLockedOut")
         pw.println("  mFaceLockedOut=$faceLockedOut")
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 49a8022..57997d8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -16,6 +16,8 @@
 
 package com.android.keyguard;
 
+import static android.app.StatusBarManager.SESSION_KEYGUARD;
+
 import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_BIOMETRIC;
 import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_EXTENDED_ACCESS;
 import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_NONE_SECURITY;
@@ -36,7 +38,10 @@
 import android.util.Slog;
 import android.view.MotionEvent;
 
+import androidx.annotation.Nullable;
+
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.InstanceId;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.nano.MetricsProto;
@@ -53,6 +58,7 @@
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -86,6 +92,7 @@
     private final UserSwitcherController mUserSwitcherController;
     private final GlobalSettings mGlobalSettings;
     private final FeatureFlags mFeatureFlags;
+    private final SessionTracker mSessionTracker;
 
     private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
 
@@ -191,7 +198,7 @@
             mMetricsLogger.write(new LogMaker(MetricsEvent.BOUNCER)
                     .setType(success ? MetricsEvent.TYPE_SUCCESS : MetricsEvent.TYPE_FAILURE));
             mUiEventLogger.log(success ? BouncerUiEvent.BOUNCER_PASSWORD_SUCCESS
-                    : BouncerUiEvent.BOUNCER_PASSWORD_FAILURE);
+                            : BouncerUiEvent.BOUNCER_PASSWORD_FAILURE, getSessionId());
         }
 
         public void reset() {
@@ -242,7 +249,8 @@
             FalsingManager falsingManager,
             UserSwitcherController userSwitcherController,
             FeatureFlags featureFlags,
-            GlobalSettings globalSettings) {
+            GlobalSettings globalSettings,
+            SessionTracker sessionTracker) {
         super(view);
         mLockPatternUtils = lockPatternUtils;
         mUpdateMonitor = keyguardUpdateMonitor;
@@ -261,6 +269,7 @@
         mUserSwitcherController = userSwitcherController;
         mFeatureFlags = featureFlags;
         mGlobalSettings = globalSettings;
+        mSessionTracker = sessionTracker;
     }
 
     @Override
@@ -456,7 +465,7 @@
                     .setType(MetricsProto.MetricsEvent.TYPE_DISMISS).setSubtype(eventSubtype));
         }
         if (uiEvent != BouncerUiEvent.UNKNOWN) {
-            mUiEventLogger.log(uiEvent);
+            mUiEventLogger.log(uiEvent, getSessionId());
         }
         if (finish) {
             mSecurityCallback.finish(strongAuth, targetUserId);
@@ -599,6 +608,10 @@
         }
     }
 
+    private @Nullable InstanceId getSessionId() {
+        return mSessionTracker.getSessionId(SESSION_KEYGUARD);
+    }
+
     /** Update keyguard position based on a tapped X coordinate. */
     public void updateKeyguardPosition(float x) {
         mView.updatePositionByTouchX(x);
@@ -622,6 +635,7 @@
         private final GlobalSettings mGlobalSettings;
         private final FeatureFlags mFeatureFlags;
         private final UserSwitcherController mUserSwitcherController;
+        private final SessionTracker mSessionTracker;
 
         @Inject
         Factory(KeyguardSecurityContainer view,
@@ -639,7 +653,8 @@
                 FalsingManager falsingManager,
                 UserSwitcherController userSwitcherController,
                 FeatureFlags featureFlags,
-                GlobalSettings globalSettings) {
+                GlobalSettings globalSettings,
+                SessionTracker sessionTracker) {
             mView = view;
             mAdminSecondaryLockScreenControllerFactory = adminSecondaryLockScreenControllerFactory;
             mLockPatternUtils = lockPatternUtils;
@@ -655,6 +670,7 @@
             mFeatureFlags = featureFlags;
             mGlobalSettings = globalSettings;
             mUserSwitcherController = userSwitcherController;
+            mSessionTracker = sessionTracker;
         }
 
         public KeyguardSecurityContainerController create(
@@ -664,7 +680,7 @@
                     mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger,
                     mKeyguardStateController, securityCallback, mSecurityViewFlipperController,
                     mConfigurationController, mFalsingCollector, mFalsingManager,
-                    mUserSwitcherController, mFeatureFlags, mGlobalSettings);
+                    mUserSwitcherController, mFeatureFlags, mGlobalSettings, mSessionTracker);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 6626f59..80a3a0e 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -35,7 +35,6 @@
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.os.Process;
 import android.os.VibrationAttributes;
-import android.os.Vibrator;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.MathUtils;
@@ -60,6 +59,7 @@
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -103,7 +103,7 @@
 
     @NonNull private CharSequence mUnlockedLabel;
     @NonNull private CharSequence mLockedLabel;
-    @Nullable private final Vibrator mVibrator;
+    @NonNull private final VibratorHelper mVibrator;
     @Nullable private final AuthRippleController mAuthRippleController;
 
     // Tracks the velocity of a touch to help filter out the touches that move too fast.
@@ -154,7 +154,7 @@
             @NonNull AccessibilityManager accessibilityManager,
             @NonNull ConfigurationController configurationController,
             @NonNull @Main DelayableExecutor executor,
-            @Nullable Vibrator vibrator,
+            @NonNull VibratorHelper vibrator,
             @Nullable AuthRippleController authRippleController,
             @NonNull @Main Resources resources
     ) {
@@ -560,7 +560,7 @@
         switch(event.getActionMasked()) {
             case MotionEvent.ACTION_DOWN:
             case MotionEvent.ACTION_HOVER_ENTER:
-                if (mVibrator != null && !mDownDetected) {
+                if (!mDownDetected) {
                     mVibrator.vibrate(
                             Process.myUid(),
                             getContext().getOpPackageName(),
@@ -647,15 +647,13 @@
             mOnGestureDetectedRunnable.run();
         }
 
-        if (mVibrator != null) {
-            // play device entry haptic (same as biometric success haptic)
-            mVibrator.vibrate(
-                    Process.myUid(),
-                    getContext().getOpPackageName(),
-                    UdfpsController.EFFECT_CLICK,
-                    "lock-icon-device-entry",
-                    TOUCH_VIBRATION_ATTRIBUTES);
-        }
+        // play device entry haptic (same as biometric success haptic)
+        mVibrator.vibrate(
+                Process.myUid(),
+                getContext().getOpPackageName(),
+                UdfpsController.EFFECT_CLICK,
+                "lock-icon-device-entry",
+                TOUCH_VIBRATION_ATTRIBUTES);
 
         mKeyguardViewController.showBouncer(/* scrim */ true);
     }
@@ -670,12 +668,9 @@
             mVelocityTracker.recycle();
             mVelocityTracker = null;
         }
-        if (mVibrator != null) {
-            mVibrator.cancel();
-        }
+        mVibrator.cancel();
     }
 
-
     private boolean inLockIconArea(MotionEvent event) {
         return mSensorTouchLocation.contains((int) event.getX(), (int) event.getY())
                 && mView.getVisibility() == View.VISIBLE;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index b0f7e55..fe5e36e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -37,6 +37,7 @@
 import android.hardware.biometrics.BiometricManager.Authenticators;
 import android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode;
 import android.hardware.biometrics.BiometricPrompt;
+import android.hardware.biometrics.IBiometricContextListener;
 import android.hardware.biometrics.IBiometricSysuiReceiver;
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.display.DisplayManager;
@@ -64,6 +65,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.DozeReceiver;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.util.concurrency.Execution;
 
@@ -96,6 +98,7 @@
     private final Handler mHandler;
     private final Execution mExecution;
     private final CommandQueue mCommandQueue;
+    private final StatusBarStateController mStatusBarStateController;
     private final ActivityTaskManager mActivityTaskManager;
     @Nullable
     private final FingerprintManager mFingerprintManager;
@@ -118,6 +121,7 @@
     @Nullable private UdfpsController mUdfpsController;
     @Nullable private IUdfpsHbmListener mUdfpsHbmListener;
     @Nullable private SidefpsController mSidefpsController;
+    @Nullable private IBiometricContextListener mBiometricContextListener;
     @VisibleForTesting
     TaskStackListener mTaskStackListener;
     @VisibleForTesting
@@ -130,7 +134,7 @@
     @Nullable private List<FingerprintSensorPropertiesInternal> mSidefpsProps;
 
     @NonNull private final SparseBooleanArray mUdfpsEnrolledForUser;
-    private SensorPrivacyManager mSensorPrivacyManager;
+    @NonNull private final SensorPrivacyManager mSensorPrivacyManager;
     private final WakefulnessLifecycle mWakefulnessLifecycle;
 
     private class BiometricTaskStackListener extends TaskStackListener {
@@ -491,6 +495,7 @@
             Provider<SidefpsController> sidefpsControllerFactory,
             @NonNull DisplayManager displayManager,
             WakefulnessLifecycle wakefulnessLifecycle,
+            @NonNull StatusBarStateController statusBarStateController,
             @Main Handler handler) {
         super(context);
         mExecution = execution;
@@ -504,6 +509,7 @@
         mSidefpsControllerFactory = sidefpsControllerFactory;
         mWindowManager = windowManager;
         mUdfpsEnrolledForUser = new SparseBooleanArray();
+
         mOrientationListener = new BiometricDisplayListener(
                 context,
                 displayManager,
@@ -514,6 +520,14 @@
                     return Unit.INSTANCE;
                 });
 
+        mStatusBarStateController = statusBarStateController;
+        mStatusBarStateController.addCallback(new StatusBarStateController.StateListener() {
+            @Override
+            public void onDozingChanged(boolean isDozing) {
+                notifyDozeChanged(isDozing);
+            }
+        });
+
         mFaceProps = mFaceManager != null ? mFaceManager.getSensorPropertiesInternal() : null;
 
         int[] faceAuthLocation = context.getResources().getIntArray(
@@ -564,6 +578,22 @@
         mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
     }
 
+    @Override
+    public void setBiometicContextListener(IBiometricContextListener listener) {
+        mBiometricContextListener = listener;
+        notifyDozeChanged(mStatusBarStateController.isDozing());
+    }
+
+    private void notifyDozeChanged(boolean isDozing) {
+        if (mBiometricContextListener != null) {
+            try {
+                mBiometricContextListener.onDozeChanged(isDozing);
+            } catch (RemoteException e) {
+                Log.w(TAG, "failed to notify initial doze state");
+            }
+        }
+    }
+
     /**
      * Stores the listener received from {@link com.android.server.display.DisplayModeDirector}.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 5ddfd75..8052c20 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -42,7 +42,6 @@
 import android.os.Trace;
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
-import android.os.Vibrator;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
@@ -64,6 +63,7 @@
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
+import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.SystemUIDialogManager;
@@ -117,7 +117,7 @@
     @NonNull private final DumpManager mDumpManager;
     @NonNull private final SystemUIDialogManager mDialogManager;
     @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    @Nullable private final Vibrator mVibrator;
+    @NonNull private final VibratorHelper mVibrator;
     @NonNull private final FalsingManager mFalsingManager;
     @NonNull private final PowerManager mPowerManager;
     @NonNull private final AccessibilityManager mAccessibilityManager;
@@ -506,7 +506,7 @@
             @NonNull AccessibilityManager accessibilityManager,
             @NonNull LockscreenShadeTransitionController lockscreenShadeTransitionController,
             @NonNull ScreenLifecycle screenLifecycle,
-            @Nullable Vibrator vibrator,
+            @NonNull VibratorHelper vibrator,
             @NonNull UdfpsHapticsSimulator udfpsHapticsSimulator,
             @NonNull Optional<UdfpsHbmProvider> hbmProvider,
             @NonNull KeyguardStateController keyguardStateController,
@@ -577,14 +577,12 @@
      */
     @VisibleForTesting
     public void playStartHaptic() {
-        if (mVibrator != null) {
-            mVibrator.vibrate(
-                    Process.myUid(),
-                    mContext.getOpPackageName(),
-                    EFFECT_CLICK,
-                    "udfps-onStart-click",
-                    VIBRATION_ATTRIBUTES);
-        }
+        mVibrator.vibrate(
+                Process.myUid(),
+                mContext.getOpPackageName(),
+                EFFECT_CLICK,
+                "udfps-onStart-click",
+                VIBRATION_ATTRIBUTES);
     }
 
     @Nullable
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt
index e231310..eaee19a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt
@@ -18,16 +18,12 @@
 
 import android.media.AudioAttributes
 import android.os.VibrationEffect
-import android.os.Vibrator
-
 import com.android.keyguard.KeyguardUpdateMonitor
-
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.commandline.Command
 import com.android.systemui.statusbar.commandline.CommandRegistry
-
 import java.io.PrintWriter
-
 import javax.inject.Inject
 
 /**
@@ -36,7 +32,7 @@
 @SysUISingleton
 class UdfpsHapticsSimulator @Inject constructor(
     commandRegistry: CommandRegistry,
-    val vibrator: Vibrator?,
+    val vibrator: VibratorHelper,
     val keyguardUpdateMonitor: KeyguardUpdateMonitor
 ) : Command {
     val sonificationEffects =
@@ -60,13 +56,13 @@
                 }
                 "success" -> {
                     // needs to be kept up to date with AcquisitionClient#SUCCESS_VIBRATION_EFFECT
-                    vibrator?.vibrate(
+                    vibrator.vibrate(
                         VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
                         sonificationEffects)
                 }
                 "error" -> {
                     // needs to be kept up to date with AcquisitionClient#ERROR_VIBRATION_EFFECT
-                    vibrator?.vibrate(
+                    vibrator.vibrate(
                         VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK),
                         sonificationEffects)
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
index a29f3e9..f87fa96 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
@@ -24,7 +24,6 @@
 import android.content.pm.PackageManager
 import android.content.pm.ResolveInfo
 import android.os.VibrationEffect
-import android.os.Vibrator
 import android.service.controls.Control
 import android.service.controls.actions.BooleanAction
 import android.service.controls.actions.CommandAction
@@ -32,16 +31,14 @@
 import android.util.Log
 import android.view.HapticFeedbackConstants
 import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.controls.ControlsMetricsLogger
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.globalactions.GlobalActionsComponent
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.wm.shell.TaskViewFactory
-import dagger.Lazy
 import java.util.Optional
 import javax.inject.Inject
 
@@ -52,14 +49,11 @@
     @Main private val uiExecutor: DelayableExecutor,
     private val activityStarter: ActivityStarter,
     private val keyguardStateController: KeyguardStateController,
-    private val globalActionsComponent: GlobalActionsComponent,
     private val taskViewFactory: Optional<TaskViewFactory>,
-    private val broadcastDispatcher: BroadcastDispatcher,
-    private val lazyUiController: Lazy<ControlsUiController>,
-    private val controlsMetricsLogger: ControlsMetricsLogger
+    private val controlsMetricsLogger: ControlsMetricsLogger,
+    private val vibrator: VibratorHelper
 ) : ControlActionCoordinator {
     private var dialog: Dialog? = null
-    private val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
     private var pendingAction: Action? = null
     private var actionsInProgress = mutableSetOf<String>()
     private val isLocked: Boolean
@@ -194,7 +188,7 @@
     }
 
     private fun vibrate(effect: VibrationEffect) {
-        bgExecutor.execute { vibrator.vibrate(effect) }
+        vibrator.vibrate(effect)
     }
 
     private fun showDetail(cvh: ControlViewHolder, pendingIntent: PendingIntent) {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 3ee0cad..2160744 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -88,16 +88,20 @@
                 }
             };
 
+    private DreamOverlayStateController mStateController;
+
     @Inject
     public DreamOverlayService(
             Context context,
             @Main Executor executor,
             DreamOverlayComponent.Factory dreamOverlayComponentFactory,
+            DreamOverlayStateController stateController,
             KeyguardUpdateMonitor keyguardUpdateMonitor) {
         mContext = context;
         mExecutor = executor;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback);
+        mStateController = stateController;
 
         final DreamOverlayComponent component =
                 dreamOverlayComponentFactory.create(mViewModelStore, mHost);
@@ -118,6 +122,7 @@
         setCurrentState(Lifecycle.State.DESTROYED);
         final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
         windowManager.removeView(mWindow.getDecorView());
+        mStateController.setOverlayActive(false);
         super.onDestroy();
     }
 
@@ -127,6 +132,7 @@
         mExecutor.execute(() -> {
             addOverlayWindowLocked(layoutParams);
             setCurrentState(Lifecycle.State.RESUMED);
+            mStateController.setOverlayActive(true);
         });
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index e838848..3e4ae57 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.dreams;
 
+import android.util.Log;
+
 import androidx.annotation.NonNull;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -30,6 +32,7 @@
 import java.util.HashSet;
 import java.util.Objects;
 import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 
 import javax.inject.Inject;
 
@@ -41,6 +44,16 @@
 @SysUISingleton
 public class DreamOverlayStateController implements
         CallbackController<DreamOverlayStateController.Callback> {
+    private static final String TAG = "DreamOverlayStateCtlr";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    public static final int STATE_DREAM_OVERLAY_ACTIVE = 1 << 0;
+
+    private static final int OP_CLEAR_STATE = 1;
+    private static final int OP_SET_STATE = 2;
+
+    private int mState;
+
     /**
      * Callback for dream overlay events.
      */
@@ -50,6 +63,12 @@
          */
         default void onComplicationsChanged() {
         }
+
+        /**
+         * Called when the dream overlay state changes.
+         */
+        default void onStateChanged() {
+        }
     }
 
     private final Executor mExecutor;
@@ -92,6 +111,14 @@
         return Collections.unmodifiableCollection(mComplications);
     }
 
+    private void notifyCallbacks(Consumer<Callback> callbackConsumer) {
+        mExecutor.execute(() -> {
+            for (Callback callback : mCallbacks) {
+                callbackConsumer.accept(callback);
+            }
+        });
+    }
+
     @Override
     public void addCallback(@NonNull Callback callback) {
         mExecutor.execute(() -> {
@@ -117,4 +144,40 @@
             mCallbacks.remove(callback);
         });
     }
+
+    /**
+     * Returns whether the overlay is active.
+     * @return {@code true} if overlay is active, {@code false} otherwise.
+     */
+    public boolean isOverlayActive() {
+        return containsState(STATE_DREAM_OVERLAY_ACTIVE);
+    }
+
+    private boolean containsState(int state) {
+        return (mState & state) != 0;
+    }
+
+    private void modifyState(int op, int state) {
+        final int existingState = mState;
+        switch (op) {
+            case OP_CLEAR_STATE:
+                mState &= ~state;
+                break;
+            case OP_SET_STATE:
+                mState |= state;
+                break;
+        }
+
+        if (existingState != mState) {
+            notifyCallbacks(callback -> callback.onStateChanged());
+        }
+    }
+
+    /**
+     * Sets whether the overlay is active.
+     * @param active {@code true} if overlay is active, {@code false} otherwise.
+     */
+    public void setOverlayActive(boolean active) {
+        modifyState(active ? OP_SET_STATE : OP_CLEAR_STATE, STATE_DREAM_OVERLAY_ACTIVE);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
index 96cf50d..4332f58 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
@@ -154,6 +154,27 @@
     int CATEGORY_SYSTEM = 1 << 1;
 
     /**
+     * The type of dream complications which can be provided by a {@link Complication}.
+     */
+    @IntDef(prefix = {"COMPLICATION_TYPE_"}, flag = true, value = {
+            COMPLICATION_TYPE_NONE,
+            COMPLICATION_TYPE_TIME,
+            COMPLICATION_TYPE_DATE,
+            COMPLICATION_TYPE_WEATHER,
+            COMPLICATION_TYPE_AIR_QUALITY,
+            COMPLICATION_TYPE_CAST_INFO
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface ComplicationType {}
+
+    int COMPLICATION_TYPE_NONE = 0;
+    int COMPLICATION_TYPE_TIME = 1;
+    int COMPLICATION_TYPE_DATE = 1 << 1;
+    int COMPLICATION_TYPE_WEATHER = 1 << 2;
+    int COMPLICATION_TYPE_AIR_QUALITY = 1 << 3;
+    int COMPLICATION_TYPE_CAST_INFO = 1 << 4;
+
+    /**
      * The {@link Host} interface specifies a way a {@link Complication} to communicate with its
      * parent entity for information and actions.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
index cb24ae6..5223f37 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
@@ -25,10 +25,13 @@
 import androidx.constraintlayout.widget.ConstraintLayout;
 import androidx.constraintlayout.widget.Constraints;
 
+import com.android.systemui.R;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.function.Consumer;
 
 import javax.inject.Inject;
 import javax.inject.Named;
@@ -102,6 +105,8 @@
 
             final int direction = getLayoutParams().getDirection();
 
+            final boolean snapsToGuide = getLayoutParams().snapsToGuide();
+
             // If no parent, view is the anchor. In this case, it is given the highest priority for
             // alignment. All alignment preferences are done in relation to the parent container.
             final boolean isRoot = head == mView;
@@ -125,6 +130,11 @@
                         } else {
                             params.startToEnd = head.getId();
                         }
+                        if (snapsToGuide
+                                && (direction == ComplicationLayoutParams.DIRECTION_DOWN
+                                || direction == ComplicationLayoutParams.DIRECTION_UP)) {
+                            params.endToStart = R.id.complication_start_guide;
+                        }
                         break;
                     case ComplicationLayoutParams.POSITION_TOP:
                         if (isRoot || direction != ComplicationLayoutParams.DIRECTION_DOWN) {
@@ -132,6 +142,11 @@
                         } else {
                             params.topToBottom = head.getId();
                         }
+                        if (snapsToGuide
+                                && (direction == ComplicationLayoutParams.DIRECTION_END
+                                || direction == ComplicationLayoutParams.DIRECTION_START)) {
+                            params.endToStart = R.id.complication_top_guide;
+                        }
                         break;
                     case ComplicationLayoutParams.POSITION_BOTTOM:
                         if (isRoot || direction != ComplicationLayoutParams.DIRECTION_UP) {
@@ -139,6 +154,11 @@
                         } else {
                             params.bottomToTop = head.getId();
                         }
+                        if (snapsToGuide
+                                && (direction == ComplicationLayoutParams.DIRECTION_END
+                                || direction == ComplicationLayoutParams.DIRECTION_START)) {
+                            params.topToBottom = R.id.complication_bottom_guide;
+                        }
                         break;
                     case ComplicationLayoutParams.POSITION_END:
                         if (isRoot || direction != ComplicationLayoutParams.DIRECTION_START) {
@@ -146,6 +166,11 @@
                         } else {
                             params.endToStart = head.getId();
                         }
+                        if (snapsToGuide
+                                && (direction == ComplicationLayoutParams.DIRECTION_UP
+                                || direction == ComplicationLayoutParams.DIRECTION_DOWN)) {
+                            params.startToEnd = R.id.complication_end_guide;
+                        }
                         break;
                 }
             });
@@ -153,6 +178,16 @@
             mView.setLayoutParams(params);
         }
 
+        private void setGuide(ConstraintLayout.LayoutParams lp, int validDirections,
+                Consumer<ConstraintLayout.LayoutParams> consumer) {
+            final ComplicationLayoutParams layoutParams = getLayoutParams();
+            if (!layoutParams.snapsToGuide()) {
+                return;
+            }
+
+            consumer.accept(lp);
+        }
+
         /**
          * Informs the {@link ViewEntry}'s parent entity to remove the {@link ViewEntry} from
          * being shown further.
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
index f9a69fa..8e8cb72 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
@@ -40,13 +40,13 @@
 
     @interface Position {}
     /** Align view with the top of parent or bottom of preceding {@link Complication}. */
-    static final int POSITION_TOP = 1 << 0;
+    public static final int POSITION_TOP = 1 << 0;
     /** Align view with the bottom of parent or top of preceding {@link Complication}. */
-    static final int POSITION_BOTTOM = 1 << 1;
+    public static final int POSITION_BOTTOM = 1 << 1;
     /** Align view with the start of parent or end of preceding {@link Complication}. */
-    static final int POSITION_START = 1 << 2;
+    public static final int POSITION_START = 1 << 2;
     /** Align view with the end of parent or start of preceding {@link Complication}. */
-    static final int POSITION_END = 1 << 3;
+    public static final int POSITION_END = 1 << 3;
 
     private static final int FIRST_POSITION = POSITION_TOP;
     private static final int LAST_POSITION = POSITION_END;
@@ -61,13 +61,13 @@
 
     @interface Direction {}
     /** Position view upward from position. */
-    static final int DIRECTION_UP = 1 << 0;
+    public static final int DIRECTION_UP = 1 << 0;
     /** Position view downward from position. */
-    static final int DIRECTION_DOWN = 1 << 1;
+    public static final int DIRECTION_DOWN = 1 << 1;
     /** Position view towards the start of the parent. */
-    static final int DIRECTION_START = 1 << 2;
+    public static final int DIRECTION_START = 1 << 2;
     /** Position view towards the end of parent. */
-    static final int DIRECTION_END = 1 << 3;
+    public static final int DIRECTION_END = 1 << 3;
 
     @Position
     private final int mPosition;
@@ -77,6 +77,8 @@
 
     private final int mWeight;
 
+    private final boolean mSnapToGuide;
+
     // Do not allow specifying opposite positions
     private static final int[] INVALID_POSITIONS =
             { POSITION_BOTTOM | POSITION_TOP, POSITION_END | POSITION_START };
@@ -104,6 +106,27 @@
      */
     public ComplicationLayoutParams(int width, int height, @Position int position,
             @Direction int direction, int weight) {
+        this(width, height, position, direction, weight, false);
+    }
+
+    /**
+     * Constructs a {@link ComplicationLayoutParams}.
+     * @param width The width {@link android.view.View.MeasureSpec} for the view.
+     * @param height The height {@link android.view.View.MeasureSpec} for the view.
+     * @param position The place within the parent container where the view should be positioned.
+     * @param direction The direction the view should be laid out from either the parent container
+     *                  or preceding view.
+     * @param weight The weight that should be considered for this view when compared to other
+     *               views. This has an impact on the placement of the view but not the rendering of
+     *               the view.
+     * @param snapToGuide When set to {@code true}, the dimension perpendicular to the direction
+     *                    will be automatically set to align with a predetermined guide for that
+     *                    side. For example, if the complication is aligned to the top end and
+     *                    direction is down, then the width of the complication will be set to span
+     *                    from the end of the parent to the guide.
+     */
+    public ComplicationLayoutParams(int width, int height, @Position int position,
+            @Direction int direction, int weight, boolean snapToGuide) {
         super(width, height);
 
         if (!validatePosition(position)) {
@@ -118,6 +141,8 @@
         mDirection = direction;
 
         mWeight = weight;
+
+        mSnapToGuide = snapToGuide;
     }
 
     /**
@@ -128,6 +153,7 @@
         mPosition = source.mPosition;
         mDirection = source.mDirection;
         mWeight = source.mWeight;
+        mSnapToGuide = source.mSnapToGuide;
     }
 
     private static boolean validateDirection(@Position int position, @Direction int direction) {
@@ -180,7 +206,19 @@
         return mPosition;
     }
 
+    /**
+     * Returns the set weight for the complication. The weight determines ordering a complication
+     * given the same position/direction.
+     */
     public int getWeight() {
         return mWeight;
     }
+
+    /**
+     * Returns whether the complication's dimension perpendicular to direction should be
+     * automatically set.
+     */
+    public boolean snapsToGuide() {
+        return mSnapToGuide;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java
new file mode 100644
index 0000000..3a2a6ef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java
@@ -0,0 +1,53 @@
+/*
+ * 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.dreams.complication;
+
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_AIR_QUALITY;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_CAST_INFO;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_DATE;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_NONE;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_TIME;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_WEATHER;
+
+import com.android.settingslib.dream.DreamBackend;
+
+/**
+ * A collection of utility methods for working with {@link Complication}.
+ */
+public class ComplicationUtils {
+    /**
+     * Converts a {@link com.android.settingslib.dream.DreamBackend.ComplicationType} to
+     * {@link ComplicationType}.
+     */
+    @Complication.ComplicationType
+    public static int convertComplicationType(@DreamBackend.ComplicationType int type) {
+        switch (type) {
+            case DreamBackend.COMPLICATION_TYPE_TIME:
+                return COMPLICATION_TYPE_TIME;
+            case DreamBackend.COMPLICATION_TYPE_DATE:
+                return COMPLICATION_TYPE_DATE;
+            case DreamBackend.COMPLICATION_TYPE_WEATHER:
+                return COMPLICATION_TYPE_WEATHER;
+            case DreamBackend.COMPLICATION_TYPE_AIR_QUALITY:
+                return COMPLICATION_TYPE_AIR_QUALITY;
+            case DreamBackend.COMPLICATION_TYPE_CAST_INFO:
+                return COMPLICATION_TYPE_CAST_INFO;
+            default:
+                return COMPLICATION_TYPE_NONE;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
index 503817a..4eb5cb9 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -34,7 +34,6 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dreams.DreamOverlayContainerView;
 import com.android.systemui.dreams.DreamOverlayStatusBarView;
-import com.android.systemui.dreams.touch.DreamTouchHandler;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.tuner.TunerService;
@@ -44,7 +43,6 @@
 import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
-import dagger.multibindings.IntoSet;
 
 /** Dagger module for {@link DreamOverlayComponent}. */
 @Module
@@ -149,12 +147,4 @@
     static Lifecycle providesLifecycle(LifecycleOwner lifecycleOwner) {
         return lifecycleOwner.getLifecycle();
     }
-
-    // TODO: This stub should be removed once there is a {@link DreamTouchHandler}
-    // implementation present.
-    @Provides
-    @IntoSet
-    static DreamTouchHandler provideDreamTouchHandler() {
-        return session -> { };
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
new file mode 100644
index 0000000..d16c8c8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
@@ -0,0 +1,273 @@
+/*
+ * 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.dreams.touch;
+
+import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING;
+import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING;
+import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_START_REGION;
+
+import android.animation.ValueAnimator;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.InputEvent;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.KeyguardBouncer;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.wm.shell.animation.FlingAnimationUtils;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * Monitor for tracking touches on the DreamOverlay to bring up the bouncer.
+ */
+public class BouncerSwipeTouchHandler implements DreamTouchHandler {
+    /**
+     * An interface for creating ValueAnimators.
+     */
+    public interface ValueAnimatorCreator {
+        /**
+         * Creates {@link ValueAnimator}.
+         */
+        ValueAnimator create(float start, float finish);
+    }
+
+    /**
+     * An interface for obtaining VelocityTrackers.
+     */
+    public interface VelocityTrackerFactory {
+        /**
+         * Obtains {@link VelocityTracker}.
+         */
+        VelocityTracker obtain();
+    }
+
+    public static final float FLING_PERCENTAGE_THRESHOLD = 0.5f;
+
+    private static final String TAG = "BouncerSwipeTouchHandler";
+    private final NotificationShadeWindowController mNotificationShadeWindowController;
+    private final float mBouncerZoneScreenPercentage;
+
+    private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    private float mCurrentExpansion;
+    private final StatusBar mStatusBar;
+
+    private VelocityTracker mVelocityTracker;
+
+    private final FlingAnimationUtils mFlingAnimationUtils;
+    private final FlingAnimationUtils mFlingAnimationUtilsClosing;
+
+    private Boolean mCapture;
+
+    private TouchSession mTouchSession;
+
+    private ValueAnimatorCreator mValueAnimatorCreator;
+
+    private VelocityTrackerFactory mVelocityTrackerFactory;
+
+    private final GestureDetector.OnGestureListener mOnGestureListener =
+            new  GestureDetector.SimpleOnGestureListener() {
+                boolean mTrack;
+                boolean mBouncerPresent;
+
+                @Override
+                public boolean onDown(MotionEvent e) {
+                    // We only consider gestures that originate from the lower portion of the
+                    // screen.
+                    final float displayHeight = mStatusBar.getDisplayHeight();
+
+                    mBouncerPresent = mStatusBar.isBouncerShowing();
+
+                    // The target zone is either at the top or bottom of the screen, dependent on
+                    // whether the bouncer is present.
+                    final float zonePercentage =
+                            Math.abs(e.getY() - (mBouncerPresent ? 0 : displayHeight))
+                                    / displayHeight;
+
+                    mTrack =  zonePercentage < mBouncerZoneScreenPercentage;
+
+                    // Never capture onDown. While this might lead to some false positive touches
+                    // being sent to other windows/layers, this is necessary to make sure the
+                    // proper touch event sequence is received by others in the event we do not
+                    // consume the sequence here.
+                    return false;
+                }
+
+                @Override
+                public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
+                        float distanceY) {
+                    // Do not handle scroll gestures if not tracking touch events.
+                    if (!mTrack) {
+                        return false;
+                    }
+
+                    if (mCapture == null) {
+                        // If the user scrolling favors a vertical direction, begin capturing
+                        // scrolls.
+                        mCapture = Math.abs(distanceY) > Math.abs(distanceX);
+
+                        if (mCapture) {
+                            // Since the user is dragging the bouncer up, set scrimmed to false.
+                            mStatusBarKeyguardViewManager.showBouncer(false);
+                        }
+                    }
+
+                    if (!mCapture) {
+                        return false;
+                    }
+
+                    // For consistency, we adopt the expansion definition found in the
+                    // PanelViewController. In this case, expansion refers to the view above the
+                    // bouncer. As that view's expansion shrinks, the bouncer appears. The bouncer
+                    // is fully hidden at full expansion (1) and fully visible when fully collapsed
+                    // (0).
+                    final float screenTravelPercentage =
+                            Math.abs((e1.getY() - e2.getY()) / mStatusBar.getDisplayHeight());
+                    setPanelExpansion(
+                            mBouncerPresent ? screenTravelPercentage : 1 - screenTravelPercentage);
+
+                    return true;
+                }
+            };
+
+    private void setPanelExpansion(float expansion) {
+        mCurrentExpansion = expansion;
+        mStatusBarKeyguardViewManager.onPanelExpansionChanged(mCurrentExpansion, false, true);
+    }
+
+    @Inject
+    public BouncerSwipeTouchHandler(
+            StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+            StatusBar statusBar,
+            NotificationShadeWindowController notificationShadeWindowController,
+            ValueAnimatorCreator valueAnimatorCreator,
+            VelocityTrackerFactory velocityTrackerFactory,
+            @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING)
+                    FlingAnimationUtils flingAnimationUtils,
+            @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING)
+                    FlingAnimationUtils flingAnimationUtilsClosing,
+            @Named(SWIPE_TO_BOUNCER_START_REGION) float swipeRegionPercentage) {
+        mStatusBar = statusBar;
+        mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+        mNotificationShadeWindowController = notificationShadeWindowController;
+        mBouncerZoneScreenPercentage = swipeRegionPercentage;
+        mFlingAnimationUtils = flingAnimationUtils;
+        mFlingAnimationUtilsClosing = flingAnimationUtilsClosing;
+        mValueAnimatorCreator = valueAnimatorCreator;
+        mVelocityTrackerFactory = velocityTrackerFactory;
+    }
+
+    @Override
+    public void onSessionStart(TouchSession session) {
+        mVelocityTracker = mVelocityTrackerFactory.obtain();
+        mTouchSession = session;
+        mVelocityTracker.clear();
+        mNotificationShadeWindowController.setForcePluginOpen(true, this);
+        session.registerGestureListener(mOnGestureListener);
+        session.registerInputListener(ev -> onMotionEvent(ev));
+
+    }
+
+    @Override
+    public void onSessionEnd(TouchSession session) {
+        mVelocityTracker.recycle();
+        mCapture = null;
+        mNotificationShadeWindowController.setForcePluginOpen(false, this);
+    }
+
+    private void onMotionEvent(InputEvent event) {
+        if (!(event instanceof MotionEvent)) {
+            Log.e(TAG, "non MotionEvent received:" + event);
+            return;
+        }
+
+        final MotionEvent motionEvent = (MotionEvent) event;
+
+        switch(motionEvent.getAction()) {
+            case MotionEvent.ACTION_UP:
+                // If we are not capturing any input, there is no need to consider animating to
+                // finish transition.
+                if (mCapture == null || !mCapture) {
+                    break;
+                }
+
+                // We must capture the resulting velocities as resetMonitor() will clear these
+                // values.
+                mVelocityTracker.computeCurrentVelocity(1000);
+                final float verticalVelocity = mVelocityTracker.getYVelocity();
+                final float horizontalVelocity = mVelocityTracker.getXVelocity();
+
+                final float velocityVector =
+                        (float) Math.hypot(horizontalVelocity, verticalVelocity);
+
+
+                final float expansion = flingRevealsOverlay(verticalVelocity, velocityVector)
+                            ? KeyguardBouncer.EXPANSION_HIDDEN : KeyguardBouncer.EXPANSION_VISIBLE;
+                flingToExpansion(verticalVelocity, expansion);
+
+                if (expansion == KeyguardBouncer.EXPANSION_HIDDEN) {
+                    mStatusBarKeyguardViewManager.reset(false);
+                }
+                mTouchSession.pop();
+                break;
+            default:
+                mVelocityTracker.addMovement(motionEvent);
+                break;
+        }
+    }
+
+    private ValueAnimator createExpansionAnimator(float targetExpansion) {
+        final ValueAnimator animator =
+                mValueAnimatorCreator.create(mCurrentExpansion, targetExpansion);
+        animator.addUpdateListener(
+                animation -> {
+                    setPanelExpansion((float) animation.getAnimatedValue());
+                });
+        return animator;
+    }
+
+    protected boolean flingRevealsOverlay(float velocity, float velocityVector) {
+        // Fully expand if the user has expanded the bouncer less than halfway or final velocity was
+        // positive, indicating an downward direction.
+        if (Math.abs(velocityVector) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
+            return mCurrentExpansion > FLING_PERCENTAGE_THRESHOLD;
+        } else {
+            return velocity > 0;
+        }
+    }
+
+    protected void flingToExpansion(float velocity, float expansion) {
+        final float viewHeight = mStatusBar.getDisplayHeight();
+        final float currentHeight = viewHeight * mCurrentExpansion;
+        final float targetHeight = viewHeight * expansion;
+
+        final ValueAnimator animator = createExpansionAnimator(expansion);
+        if (expansion == KeyguardBouncer.EXPANSION_HIDDEN) {
+            // The animation utils deal in pixel units, rather than expansion height.
+            mFlingAnimationUtils.apply(animator, currentHeight, targetHeight, velocity, viewHeight);
+        } else {
+            mFlingAnimationUtilsClosing.apply(
+                    animator, mCurrentExpansion, currentHeight, targetHeight, viewHeight);
+        }
+
+        animator.start();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java
new file mode 100644
index 0000000..b9436f9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java
@@ -0,0 +1,128 @@
+/*
+ * 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.dreams.touch.dagger;
+
+import android.animation.ValueAnimator;
+import android.content.res.Resources;
+import android.util.TypedValue;
+import android.view.VelocityTracker;
+
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.touch.BouncerSwipeTouchHandler;
+import com.android.systemui.dreams.touch.DreamTouchHandler;
+import com.android.systemui.statusbar.phone.PanelViewController;
+import com.android.wm.shell.animation.FlingAnimationUtils;
+
+import javax.inject.Named;
+import javax.inject.Provider;
+
+import dagger.Module;
+import dagger.Provides;
+import dagger.multibindings.IntoSet;
+
+/**
+ * This module captures the components associated with {@link BouncerSwipeTouchHandler}.
+ */
+@Module
+public class BouncerSwipeModule {
+    /**
+     * The region, defined as the percentage of the screen, from which a touch gesture to start
+     * swiping up to the bouncer can occur.
+     */
+    public static final String SWIPE_TO_BOUNCER_START_REGION = "swipe_to_bouncer_start_region";
+
+    /**
+     * The {@link android.view.animation.AnimationUtils} for animating the bouncer closing.
+     */
+    public static final String SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING =
+            "swipe_to_bouncer_fling_animation_utils_closing";
+
+    /**
+     * The {@link android.view.animation.AnimationUtils} for animating the bouncer opening.
+     */
+    public static final String SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING =
+                    "swipe_to_bouncer_fling_animation_utils_opening";
+
+    /**
+     * Provides {@link BouncerSwipeTouchHandler} for inclusion in touch handling over the dream.
+     */
+    @Provides
+    @IntoSet
+    public static DreamTouchHandler providesBouncerSwipeTouchHandler(
+            BouncerSwipeTouchHandler touchHandler) {
+        return touchHandler;
+    }
+
+    /**
+     * Provides {@link android.view.animation.AnimationUtils} for closing.
+     */
+    @Provides
+    @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING)
+    public static FlingAnimationUtils providesSwipeToBouncerFlingAnimationUtilsClosing(
+            Provider<FlingAnimationUtils.Builder> flingAnimationUtilsBuilderProvider) {
+        return flingAnimationUtilsBuilderProvider.get()
+                .reset()
+                .setMaxLengthSeconds(PanelViewController.FLING_CLOSING_MAX_LENGTH_SECONDS)
+                .setSpeedUpFactor(PanelViewController.FLING_SPEED_UP_FACTOR)
+                .build();
+    }
+
+    /**
+     * Provides {@link android.view.animation.AnimationUtils} for opening.
+     */
+    @Provides
+    @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING)
+    public static FlingAnimationUtils providesSwipeToBouncerFlingAnimationUtilsOpening(
+            Provider<FlingAnimationUtils.Builder> flingAnimationUtilsBuilderProvider) {
+        return flingAnimationUtilsBuilderProvider.get()
+                .reset()
+                .setMaxLengthSeconds(PanelViewController.FLING_MAX_LENGTH_SECONDS)
+                .setSpeedUpFactor(PanelViewController.FLING_SPEED_UP_FACTOR)
+                .build();
+    }
+
+    /**
+     * Provides the region to start swipe gestures from.
+     */
+    @Provides
+    @Named(SWIPE_TO_BOUNCER_START_REGION)
+    public static float providesSwipeToBouncerStartRegion(@Main Resources resources) {
+        TypedValue typedValue = new TypedValue();
+        resources.getValue(R.dimen.dream_overlay_bouncer_start_region_screen_percentage,
+                typedValue, true);
+        return typedValue.getFloat();
+    }
+
+    /**
+     * Provides the default {@link BouncerSwipeTouchHandler.ValueAnimatorCreator}, which is simply
+     * a wrapper around {@link ValueAnimator}.
+     */
+    @Provides
+    public static BouncerSwipeTouchHandler.ValueAnimatorCreator providesValueAnimatorCreator() {
+        return (start, finish) -> ValueAnimator.ofFloat(start, finish);
+    }
+
+    /**
+     * Provides the default {@link BouncerSwipeTouchHandler.VelocityTrackerFactory}. which is a
+     * passthrough to {@link android.view.VelocityTracker}.
+     */
+    @Provides
+    public static BouncerSwipeTouchHandler.VelocityTrackerFactory providesVelocityTrackerFactory() {
+        return () -> VelocityTracker.obtain();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java
index 7b77b59..dad0004 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java
@@ -21,8 +21,10 @@
 /**
  * {@link DreamTouchModule} encapsulates dream touch-related components.
  */
-@Module(subcomponents = {
-        InputSessionComponent.class,
+@Module(includes = {
+            BouncerSwipeModule.class,
+        }, subcomponents = {
+            InputSessionComponent.class,
 })
 public interface DreamTouchModule {
     String INPUT_SESSION_NAME = "INPUT_SESSION_NAME";
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 2ebcd853..f0371fc 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -62,7 +62,6 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.os.Vibrator;
 import android.provider.Settings;
 import android.service.dreams.IDreamManager;
 import android.sysprop.TelephonyProperties;
@@ -119,6 +118,7 @@
 import com.android.systemui.plugins.GlobalActionsPanelPlugin;
 import com.android.systemui.scrim.ScrimDrawable;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.statusbar.phone.SystemUIDialogManager;
@@ -327,7 +327,7 @@
             TelephonyListenerManager telephonyListenerManager,
             GlobalSettings globalSettings,
             SecureSettings secureSettings,
-            @Nullable Vibrator vibrator,
+            @NonNull VibratorHelper vibrator,
             @Main Resources resources,
             ConfigurationController configurationController,
             KeyguardStateController keyguardStateController,
@@ -397,7 +397,7 @@
         mGlobalSettings.registerContentObserver(
                 Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true,
                 mAirplaneModeObserver);
-        mHasVibrator = vibrator != null && vibrator.hasVibrator();
+        mHasVibrator = vibrator.hasVibrator();
 
         mShowSilentToggle = SHOW_SILENT_TOGGLE && !resources.getBoolean(
                 R.bool.config_useFixedVolume);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 701d139..fd2c6dd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -106,6 +106,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.dagger.qualifiers.UiBackground;
+import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.dagger.KeyguardModule;
 import com.android.systemui.navigationbar.NavigationModeController;
@@ -138,7 +139,7 @@
  * state of the keyguard, power management events that effect whether the keyguard
  * should be shown or reset, callbacks to the phone window manager to notify
  * it of when the keyguard is showing, and events from the keyguard view itself
- * stating that the keyguard was succesfully unlocked.
+ * stating that the keyguard was successfully unlocked.
  *
  * Note that the keyguard view is shown when the screen is off (as appropriate)
  * so that once the screen comes on, it will be ready immediately.
@@ -152,15 +153,15 @@
  * - the keyguard is showing
  *
  * Example external events that translate to keyguard view changes:
- * - screen turned off -> reset the keyguard, and show it so it will be ready
+ * - screen turned off -> reset the keyguard, and show it, so it will be ready
  *   next time the screen turns on
  * - keyboard is slid open -> if the keyguard is not secure, hide it
  *
  * Events from the keyguard view:
- * - user succesfully unlocked keyguard -> hide keyguard view, and no longer
+ * - user successfully unlocked keyguard -> hide keyguard view, and no longer
  *   restrict input events.
  *
- * Note: in addition to normal power managment events that effect the state of
+ * Note: in addition to normal power management events that effect the state of
  * whether the keyguard should be showing, external apps and services may request
  * that the keyguard be disabled via {@link #setKeyguardEnabled(boolean)}.  When
  * false, this will override all other conditions for turning on the keyguard.
@@ -224,7 +225,7 @@
     /**
      * How long we'll wait for the {@link ViewMediatorCallback#keyguardDoneDrawing()}
      * callback before unblocking a call to {@link #setKeyguardEnabled(boolean)}
-     * that is reenabling the keyguard.
+     * that is re-enabling the keyguard.
      */
     private static final int KEYGUARD_DONE_DRAWING_TIMEOUT_MS = 2000;
 
@@ -233,6 +234,7 @@
      * keyguard to show even if it is disabled for the current user.
      */
     public static final String OPTION_FORCE_SHOW = "force_show";
+    private final DreamOverlayStateController mDreamOverlayStateController;
 
     /** The stream type that the lock sounds are tied to. */
     private int mUiSoundsStreamType;
@@ -273,14 +275,14 @@
     // these are protected by synchronized (this)
 
     /**
-     * External apps (like the phone app) can tell us to disable the keygaurd.
+     * External apps (like the phone app) can tell us to disable the keyguard.
      */
     private boolean mExternallyEnabled = true;
 
     /**
      * Remember if an external call to {@link #setKeyguardEnabled} with value
      * false caused us to hide the keyguard, so that we need to reshow it once
-     * the keygaurd is reenabled with another call with value true.
+     * the keyguard is re-enabled with another call with value true.
      */
     private boolean mNeedToReshowWhenReenabled = false;
 
@@ -291,6 +293,9 @@
     // AOD is enabled and status bar is in AOD state.
     private boolean mAodShowing;
 
+    // Dream overlay is visible.
+    private boolean mDreamOverlayShowing;
+
     /** Cached value of #isInputRestricted */
     private boolean mInputRestricted;
 
@@ -304,7 +309,7 @@
     private int mDelayedShowingSequence;
 
     /**
-     * Simiar to {@link #mDelayedProfileShowingSequence}, but it is for profile case.
+     * Similar to {@link #mDelayedProfileShowingSequence}, but it is for profile case.
      */
     private int mDelayedProfileShowingSequence;
 
@@ -341,7 +346,7 @@
     private String mPhoneState = TelephonyManager.EXTRA_STATE_IDLE;
 
     /**
-     * Whether a hide is pending an we are just waiting for #startKeyguardExitAnimation to be
+     * Whether a hide is pending and we are just waiting for #startKeyguardExitAnimation to be
      * called.
      * */
     private boolean mHiding;
@@ -355,7 +360,7 @@
                     | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
 
     /**
-     * {@link #setKeyguardEnabled} waits on this condition when it reenables
+     * {@link #setKeyguardEnabled} waits on this condition when it re-enables
      * the keyguard.
      */
     private boolean mWaitingUntilKeyguardVisible = false;
@@ -470,6 +475,14 @@
             }
     };
 
+    private final DreamOverlayStateController.Callback mDreamOverlayStateCallback =
+            new DreamOverlayStateController.Callback() {
+                @Override
+                public void onStateChanged() {
+                    mDreamOverlayShowing = mDreamOverlayStateController.isOverlayActive();
+                }
+            };
+
     KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() {
 
         @Override
@@ -494,7 +507,7 @@
             synchronized (KeyguardViewMediator.this) {
                 resetKeyguardDonePendingLocked();
                 if (mLockPatternUtils.isLockScreenDisabled(userId)) {
-                    // If we switching to a user that has keyguard disabled, dismiss keyguard.
+                    // If we are switching to a user that has keyguard disabled, dismiss keyguard.
                     dismiss(null /* callback */, null /* message */);
                 } else {
                     resetStateLocked();
@@ -508,7 +521,7 @@
             if (DEBUG) Log.d(TAG, String.format("onUserSwitchComplete %d", userId));
             if (userId != UserHandle.USER_SYSTEM) {
                 UserInfo info = UserManager.get(mContext).getUserInfo(userId);
-                // Don't try to dismiss if the user has Pin/Patter/Password set
+                // Don't try to dismiss if the user has Pin/Pattern/Password set
                 if (info == null || mLockPatternUtils.isSecure(userId)) {
                     return;
                 } else if (info.isGuest() || info.isDemo()) {
@@ -836,6 +849,7 @@
             Lazy<NotificationShadeDepthController> notificationShadeDepthController,
             ScreenOnCoordinator screenOnCoordinator,
             InteractionJankMonitor interactionJankMonitor,
+            DreamOverlayStateController dreamOverlayStateController,
             Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy) {
         super(context);
         mFalsingCollector = falsingCollector;
@@ -875,6 +889,7 @@
         mKeyguardUnlockAnimationControllerLazy = keyguardUnlockAnimationControllerLazy;
         mScreenOffAnimationController = screenOffAnimationController;
         mInteractionJankMonitor = interactionJankMonitor;
+        mDreamOverlayStateController = dreamOverlayStateController;
     }
 
     public void userActivity() {
@@ -980,6 +995,7 @@
             mSystemReady = true;
             doKeyguardLocked(null);
             mUpdateMonitor.registerCallback(mUpdateCallback);
+            mDreamOverlayStateController.addCallback(mDreamOverlayStateCallback);
         }
         // Most services aren't available until the system reaches the ready state, so we
         // send it here when the device first boots.
@@ -1041,7 +1057,7 @@
 
         mUpdateMonitor.dispatchStartedGoingToSleep(offReason);
 
-        // Reset keyguard going away state so we can start listening for fingerprint. We
+        // Reset keyguard going away state, so we can start listening for fingerprint. We
         // explicitly DO NOT want to call
         // mKeyguardViewControllerLazy.get().setKeyguardGoingAwayState(false)
         // here, since that will mess with the device lock state.
@@ -1121,9 +1137,9 @@
     }
 
     private long getLockTimeout(int userId) {
-        // if the screen turned off because of timeout or the user hit the power button
+        // if the screen turned off because of timeout or the user hit the power button,
         // and we don't need to lock immediately, set an alarm
-        // to enable it a little bit later (i.e, give the user a chance
+        // to enable it a bit later (i.e, give the user a chance
         // to turn the screen back on within a certain window without
         // having to unlock the screen)
         final ContentResolver cr = mContext.getContentResolver();
@@ -1217,7 +1233,7 @@
     }
 
     /**
-     * Let's us know when the device is waking up.
+     * It will let us know when the device is waking up.
      */
     public void onStartedWakingUp(boolean cameraGestureTriggered) {
         Trace.beginSection("KeyguardViewMediator#onStartedWakingUp");
@@ -1299,7 +1315,7 @@
                 if (mExitSecureCallback != null) {
                     if (DEBUG) Log.d(TAG, "in process of verifyUnlock request, ignoring");
                     // we're in the process of handling a request to verify the user
-                    // can get past the keyguard. ignore extraneous requests to disable / reenable
+                    // can get past the keyguard. ignore extraneous requests to disable / re-enable
                     return;
                 }
 
@@ -1310,7 +1326,7 @@
                 updateInputRestrictedLocked();
                 hideLocked();
             } else if (enabled && mNeedToReshowWhenReenabled) {
-                // reenabled after previously hidden, reshow
+                // re-enabled after previously hidden, reshow
                 if (DEBUG) Log.d(TAG, "previously hidden, reshowing, reenabling "
                         + "status bar expansion");
                 mNeedToReshowWhenReenabled = false;
@@ -1328,8 +1344,8 @@
                 } else {
                     showLocked(null);
 
-                    // block until we know the keygaurd is done drawing (and post a message
-                    // to unblock us after a timeout so we don't risk blocking too long
+                    // block until we know the keyguard is done drawing (and post a message
+                    // to unblock us after a timeout, so we don't risk blocking too long
                     // and causing an ANR).
                     mWaitingUntilKeyguardVisible = true;
                     mHandler.sendEmptyMessageDelayed(KEYGUARD_DONE_DRAWING, KEYGUARD_DONE_DRAWING_TIMEOUT_MS);
@@ -1917,7 +1933,7 @@
 
             mExitSecureCallback = null;
 
-            // after succesfully exiting securely, no need to reshow
+            // after successfully exiting securely, no need to reshow
             // the keyguard when they've released the lock
             mExternallyEnabled = true;
             mNeedToReshowWhenReenabled = false;
@@ -1992,7 +2008,7 @@
                 if (mAudioManager.isStreamMute(mUiSoundsStreamType)) return;
 
                 int id = mLockSounds.play(soundId,
-                        mLockSoundVolume, mLockSoundVolume, 1/*priortiy*/, 0/*loop*/, 1.0f/*rate*/);
+                        mLockSoundVolume, mLockSoundVolume, 1/*priority*/, 0/*loop*/, 1.0f/*rate*/);
                 synchronized (this) {
                     mLockSoundStreamId = id;
                 }
@@ -2078,7 +2094,7 @@
                     || mScreenOnCoordinator.getWakeAndUnlocking()
                             && mWallpaperSupportsAmbientMode) {
                 // When the wallpaper supports ambient mode, the scrim isn't fully opaque during
-                // wake and unlock and we should fade in the app on top of the wallpaper
+                // wake and unlock, and we should fade in the app on top of the wallpaper
                 flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
             }
             if (mKeyguardViewControllerLazy.get().isUnlockWithWallpaper()) {
@@ -2123,7 +2139,7 @@
         Trace.beginSection("KeyguardViewMediator#handleHide");
 
         // It's possible that the device was unlocked in a dream state. It's time to wake up.
-        if (mAodShowing) {
+        if (mAodShowing || mDreamOverlayShowing) {
             PowerManager pm = mContext.getSystemService(PowerManager.class);
             pm.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
                     "com.android.systemui:BOUNCER_DOZING");
@@ -2167,15 +2183,15 @@
         synchronized (KeyguardViewMediator.this) {
 
             // Tell ActivityManager that we canceled the keyguard animation if
-            // handleStartKeyguardExitAnimation was called but we're not hiding the keyguard, unless
-            // we're animating the surface behind the keyguard and will be hiding the keyguard
-            // shortly.
+            // handleStartKeyguardExitAnimation was called, but we're not hiding the keyguard,
+            // unless we're animating the surface behind the keyguard and will be hiding the
+            // keyguard shortly.
             if (!mHiding
                     && !mSurfaceBehindRemoteAnimationRequested
                     && !mKeyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture()) {
                 if (finishedCallback != null) {
                     // There will not execute animation, send a finish callback to ensure the remote
-                    // animation won't hanging there.
+                    // animation won't hang there.
                     try {
                         finishedCallback.onAnimationFinished();
                     } catch (RemoteException e) {
@@ -2192,7 +2208,7 @@
             if (mScreenOnCoordinator.getWakeAndUnlocking()) {
 
                 // Hack level over 9000: To speed up wake-and-unlock sequence, force it to report
-                // the next draw from here so we don't have to wait for window manager to signal
+                // the next draw from here, so we don't have to wait for window manager to signal
                 // this to our ViewRootImpl.
                 mKeyguardViewControllerLazy.get().getViewRootImpl().setReportNextDraw();
                 mScreenOnCoordinator.setWakeAndUnlocking(false);
@@ -2344,7 +2360,7 @@
     }
 
     /**
-     * Called if the keyguard exit animation has been cancelled and we should dismiss to the
+     * Called if the keyguard exit animation has been cancelled, and we should dismiss to the
      * keyguard.
      *
      * This can happen due to the system cancelling the RemoteAnimation (due to a timeout, a new
@@ -2595,7 +2611,7 @@
     }
 
     /**
-     * Notifies to System UI that the activity behind has now been drawn and it's safe to remove
+     * Notifies to System UI that the activity behind has now been drawn, and it's safe to remove
      * the wallpaper and keyguard flag, and WindowManager has started running keyguard exit
      * animation.
      *
@@ -2609,7 +2625,7 @@
     }
 
     /**
-     * Notifies to System UI that the activity behind has now been drawn and it's safe to remove
+     * Notifies to System UI that the activity behind has now been drawn, and it's safe to remove
      * the wallpaper and keyguard flag, and System UI should start running keyguard exit animation.
      *
      * @param apps The list of apps to animate.
@@ -2625,7 +2641,7 @@
     }
 
     /**
-     * Notifies to System UI that the activity behind has now been drawn and it's safe to remove
+     * Notifies to System UI that the activity behind has now been drawn, and it's safe to remove
      * the wallpaper and keyguard flag, and start running keyguard exit animation.
      *
      * @param startTime the start time of the animation in uptime milliseconds. Deprecated.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index f14d130..b49b49cb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -37,6 +37,7 @@
 import com.android.systemui.classifier.FalsingModule;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.UiBackground;
+import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
@@ -100,6 +101,7 @@
             Lazy<NotificationShadeDepthController> notificationShadeDepthController,
             ScreenOnCoordinator screenOnCoordinator,
             InteractionJankMonitor interactionJankMonitor,
+            DreamOverlayStateController dreamOverlayStateController,
             Lazy<NotificationShadeWindowController> notificationShadeWindowController) {
         return new KeyguardViewMediator(
                 context,
@@ -125,6 +127,7 @@
                 notificationShadeDepthController,
                 screenOnCoordinator,
                 interactionJankMonitor,
+                dreamOverlayStateController,
                 notificationShadeWindowController
         );
     }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 441e79a..ec15b24 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -31,6 +31,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IMMERSIVE_MODE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
@@ -304,6 +305,7 @@
                         allowSystemGestureIgnoringBarVisibility())
                 .setFlag(SYSUI_STATE_SCREEN_PINNING,
                         ActivityManagerWrapper.getInstance().isScreenPinningActive())
+                .setFlag(SYSUI_STATE_IMMERSIVE_MODE, isImmersiveMode())
                 .commitUpdate(mDisplayId);
     }
 
@@ -445,6 +447,10 @@
         return mBehavior != BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
     }
 
+    private boolean isImmersiveMode() {
+        return mBehavior == BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
+    }
+
     @Override
     public void onConfigurationChanged(Configuration configuration) {
         mEdgeBackGestureHandler.onConfigurationChanged(configuration);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 597e424..9d43d30 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -37,6 +37,7 @@
 import android.graphics.drawable.Icon;
 import android.hardware.biometrics.BiometricAuthenticator.Modality;
 import android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode;
+import android.hardware.biometrics.IBiometricContextListener;
 import android.hardware.biometrics.IBiometricSysuiReceiver;
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.display.DisplayManager;
@@ -154,6 +155,7 @@
     private static final int MSG_SET_UDFPS_HBM_LISTENER = 60 << MSG_SHIFT;
     private static final int MSG_TILE_SERVICE_REQUEST_ADD = 61 << MSG_SHIFT;
     private static final int MSG_TILE_SERVICE_REQUEST_CANCEL = 62 << MSG_SHIFT;
+    private static final int MSG_SET_BIOMETRICS_LISTENER = 63 << MSG_SHIFT;
 
     public static final int FLAG_EXCLUDE_NONE = 0;
     public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -317,6 +319,12 @@
         }
 
         /**
+         * @see IStatusBar#setBiometicContextListener(IBiometricContextListener)
+         */
+        default void setBiometicContextListener(IBiometricContextListener listener) {
+        }
+
+        /**
          * @see IStatusBar#setUdfpsHbmListener(IUdfpsHbmListener)
          */
         default void setUdfpsHbmListener(IUdfpsHbmListener listener) {
@@ -958,6 +966,13 @@
     }
 
     @Override
+    public void setBiometicContextListener(IBiometricContextListener listener) {
+        synchronized (mLock) {
+            mHandler.obtainMessage(MSG_SET_BIOMETRICS_LISTENER, listener).sendToTarget();
+        }
+    }
+
+    @Override
     public void setUdfpsHbmListener(IUdfpsHbmListener listener) {
         synchronized (mLock) {
             mHandler.obtainMessage(MSG_SET_UDFPS_HBM_LISTENER, listener).sendToTarget();
@@ -1411,6 +1426,12 @@
                         mCallbacks.get(i).hideAuthenticationDialog();
                     }
                     break;
+                case MSG_SET_BIOMETRICS_LISTENER:
+                    for (int i = 0; i < mCallbacks.size(); i++) {
+                        mCallbacks.get(i).setBiometicContextListener(
+                                (IBiometricContextListener) msg.obj);
+                    }
+                    break;
                 case MSG_SET_UDFPS_HBM_LISTENER:
                     for (int i = 0; i < mCallbacks.size(); i++) {
                         mCallbacks.get(i).setUdfpsHbmListener((IUdfpsHbmListener) msg.obj);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index c136d9c..b312ce2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -337,11 +337,11 @@
             if (field != value || forceApplyAmount) {
                 field = value
                 if (!nsslController.isInLockedDownShade() || field == 0f || forceApplyAmount) {
-                    nsslController.setTransitionToFullShadeAmount(field)
+                    qSDragProgress = MathUtils.saturate(dragDownAmount / scrimTransitionDistance)
+                    nsslController.setTransitionToFullShadeAmount(field, qSDragProgress)
+                    qS.setTransitionToFullShadeAmount(field, qSDragProgress)
                     notificationPanelController.setTransitionToFullShadeAmount(field,
                             false /* animate */, 0 /* delay */)
-                    qSDragProgress = MathUtils.saturate(dragDownAmount / scrimTransitionDistance)
-                    qS.setTransitionToFullShadeAmount(field, qSDragProgress)
                     // TODO: appear media also in split shade
                     val mediaAmount = if (useSplitShade) 0f else field
                     mediaHierarchyManager.setTransitionToFullShadeAmount(mediaAmount)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 51a66aa..3411eab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar;
 
+import static com.android.systemui.statusbar.phone.NotificationIconContainer.MAX_ICONS_ON_LOCKSCREEN;
+
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -32,6 +34,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.SystemBarUtils;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.animation.ShadeInterpolation;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -45,6 +48,7 @@
 import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
 import com.android.systemui.statusbar.notification.stack.ViewState;
 import com.android.systemui.statusbar.phone.NotificationIconContainer;
+import com.android.systemui.util.Utils;
 
 /**
  * A notification shelf view that is placed inside the notification scroller. It manages the
@@ -81,6 +85,11 @@
     private int mIndexOfFirstViewInShelf = -1;
     private float mCornerAnimationDistance;
     private NotificationShelfController mController;
+    private int mActualWidth = -1;
+    private boolean mUseSplitShade;
+
+    /** Fraction of lockscreen to shade animation (on lockscreen swipe down). */
+    private float mFractionToShade;
 
     public NotificationShelf(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -122,13 +131,16 @@
         layoutParams.height = res.getDimensionPixelOffset(R.dimen.notification_shelf_height);
         setLayoutParams(layoutParams);
 
-        int padding = res.getDimensionPixelOffset(R.dimen.shelf_icon_container_padding);
+        final int padding = res.getDimensionPixelOffset(R.dimen.shelf_icon_container_padding);
         mShelfIcons.setPadding(padding, 0, padding, 0);
         mScrollFastThreshold = res.getDimensionPixelOffset(R.dimen.scroll_fast_threshold);
         mShowNotificationShelf = res.getBoolean(R.bool.config_showNotificationShelf);
         mCornerAnimationDistance = res.getDimensionPixelSize(
                 R.dimen.notification_corner_animation_distance);
 
+        // TODO(b/213480466)  enable short shelf on split shade
+        mUseSplitShade = Utils.shouldUseSplitNotificationShade(mContext.getResources());
+
         mShelfIcons.setInNotificationIconShelf(true);
         if (!mShowNotificationShelf) {
             setVisibility(GONE);
@@ -203,6 +215,10 @@
 
             final float stackEnd = ambientState.getStackY() + ambientState.getStackHeight();
             viewState.yTranslation = stackEnd - viewState.height;
+
+            final int shortestWidth = mShelfIcons.calculateWidthFor(MAX_ICONS_ON_LOCKSCREEN);
+            final float fraction = Interpolators.STANDARD.getInterpolation(mFractionToShade);
+            updateStateWidth(viewState, fraction, shortestWidth);
         } else {
             viewState.hidden = true;
             viewState.location = ExpandableViewState.LOCATION_GONE;
@@ -211,6 +227,77 @@
     }
 
     /**
+     * @param shelfState View state for NotificationShelf
+     * @param fraction Fraction of lockscreen to shade transition
+     * @param shortestWidth Shortest width to use for lockscreen shelf
+     */
+    @VisibleForTesting
+    public void updateStateWidth(ShelfState shelfState, float fraction, int shortestWidth) {
+        shelfState.actualWidth = !mUseSplitShade && mAmbientState.isOnKeyguard()
+                ? (int) MathUtils.lerp(shortestWidth, getWidth(), fraction)
+                : getWidth();
+    }
+
+    /**
+     * @param fractionToShade Fraction of lockscreen to shade transition
+     */
+    public void setFractionToShade(float fractionToShade) {
+        mFractionToShade = fractionToShade;
+    }
+
+    /**
+     * @return Actual width of shelf, accounting for possible ongoing width animation
+     */
+    public int getActualWidth() {
+        return mActualWidth > -1 ? mActualWidth : getWidth();
+    }
+
+    /**
+     * @param localX Click x from left of screen
+     * @param slop Margin of error within which we count x for valid click
+     * @param left Left of shelf, from left of screen
+     * @param right Right of shelf, from left of screen
+     * @return Whether click x was in view
+     */
+    @VisibleForTesting
+    public boolean isXInView(float localX, float slop, float left, float right) {
+        return (left - slop) <= localX && localX < (right + slop);
+    }
+
+    /**
+     * @param localY Click y from top of shelf
+     * @param slop Margin of error within which we count y for valid click
+     * @param top Top of shelf
+     * @param bottom Height of shelf
+     * @return Whether click y was in view
+     */
+    @VisibleForTesting
+    public boolean isYInView(float localY, float slop, float top, float bottom) {
+        return (top - slop) <= localY && localY < (bottom + slop);
+    }
+
+    /**
+     * @param localX Click x
+     * @param localY Click y
+     * @param slop Margin of error for valid click
+     * @return Whether this click was on the visible (non-clipped) part of the shelf
+     */
+    @Override
+    public boolean pointInView(float localX, float localY, float slop) {
+        final float containerWidth = getWidth();
+        final float shelfWidth = getActualWidth();
+
+        final float left = isLayoutRtl() ? containerWidth - shelfWidth : 0;
+        final float right = isLayoutRtl() ? containerWidth : shelfWidth;
+
+        final float top = mClipTopAmount;
+        final float bottom = getActualHeight();
+
+        return isXInView(localX, slop, left, right)
+                && isYInView(localY, slop, top, bottom);
+    }
+
+    /**
      * Update the shelf appearance based on the other notifications around it. This transforms
      * the icons from the notification area into the shelf.
      */
@@ -732,11 +819,15 @@
         // we always want to clip to our sides, such that nothing can draw outside of these bounds
         int height = getResources().getDisplayMetrics().heightPixels;
         mClipRect.set(0, -height, getWidth(), height);
-        mShelfIcons.setClipBounds(mClipRect);
+        if (mShelfIcons != null) {
+            mShelfIcons.setClipBounds(mClipRect);
+        }
     }
 
     private void updateRelativeOffset() {
-        mCollapsedIcons.getLocationOnScreen(mTmp);
+        if (mCollapsedIcons != null) {
+            mCollapsedIcons.getLocationOnScreen(mTmp);
+        }
         getLocationOnScreen(mTmp);
     }
 
@@ -831,9 +922,20 @@
         mIndexOfFirstViewInShelf = mHostLayoutController.indexOfChild(firstViewInShelf);
     }
 
-    private class ShelfState extends ExpandableViewState {
+    public class ShelfState extends ExpandableViewState {
         private boolean hasItemsInStableShelf;
         private ExpandableView firstViewInShelf;
+        public int actualWidth = -1;
+
+        private void updateShelfWidth(View view) {
+            if (actualWidth < 0) {
+                return;
+            }
+            mActualWidth = actualWidth;
+            ActivatableNotificationView anv = (ActivatableNotificationView) view;
+            anv.getBackgroundNormal().setActualWidth(actualWidth);
+            mShelfIcons.setActualLayoutWidth(actualWidth);
+        }
 
         @Override
         public void applyToView(View view) {
@@ -846,19 +948,21 @@
             updateAppearance();
             setHasItemsInStableShelf(hasItemsInStableShelf);
             mShelfIcons.setAnimationsEnabled(mAnimationsEnabled);
+            updateShelfWidth(view);
         }
 
         @Override
-        public void animateTo(View child, AnimationProperties properties) {
+        public void animateTo(View view, AnimationProperties properties) {
             if (!mShowNotificationShelf) {
                 return;
             }
 
-            super.animateTo(child, properties);
+            super.animateTo(view, properties);
             setIndexOfFirstViewInShelf(firstViewInShelf);
             updateAppearance();
             setHasItemsInStableShelf(hasItemsInStableShelf);
             mShelfIcons.setAnimationsEnabled(mAnimationsEnabled);
+            updateShelfWidth(view);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
index 6c3a909..c74621d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
@@ -16,13 +16,19 @@
 
 package com.android.systemui.statusbar;
 
-import android.content.Context;
-import android.os.AsyncTask;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.media.AudioAttributes;
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
@@ -32,21 +38,75 @@
 public class VibratorHelper {
 
     private final Vibrator mVibrator;
-    private final Context mContext;
     private static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES =
             VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH);
+    private final Executor mExecutor;
 
     /**
      */
     @Inject
-    public VibratorHelper(Context context) {
-        mContext = context;
-        mVibrator = context.getSystemService(Vibrator.class);
+    public VibratorHelper(@Nullable Vibrator vibrator, @Background Executor executor) {
+        mExecutor = executor;
+        mVibrator = vibrator;
     }
 
+    /**
+     * @see Vibrator#vibrate(long)
+     */
     public void vibrate(final int effectId) {
-        AsyncTask.execute(() ->
+        if (!hasVibrator()) {
+            return;
+        }
+        mExecutor.execute(() ->
                 mVibrator.vibrate(VibrationEffect.get(effectId, false /* fallback */),
                         TOUCH_VIBRATION_ATTRIBUTES));
     }
+
+    /**
+     * @see Vibrator#vibrate(int, String, VibrationEffect, String, VibrationAttributes)
+     */
+    public void vibrate(int uid, String opPkg, @NonNull VibrationEffect vibe,
+            String reason, @NonNull VibrationAttributes attributes) {
+        if (!hasVibrator()) {
+            return;
+        }
+        mExecutor.execute(() -> mVibrator.vibrate(uid, opPkg, vibe, reason, attributes));
+    }
+
+    /**
+     * @see Vibrator#vibrate(VibrationEffect, AudioAttributes)
+     */
+    public void vibrate(@NonNull VibrationEffect effect, @NonNull AudioAttributes attributes) {
+        if (!hasVibrator()) {
+            return;
+        }
+        mExecutor.execute(() -> mVibrator.vibrate(effect, attributes));
+    }
+
+    /**
+     * @see Vibrator#vibrate(VibrationEffect)
+     */
+    public void vibrate(@NotNull VibrationEffect effect) {
+        if (!hasVibrator()) {
+            return;
+        }
+        mExecutor.execute(() -> mVibrator.vibrate(effect));
+    }
+
+    /**
+     * @see Vibrator#hasVibrator()
+     */
+    public boolean hasVibrator() {
+        return mVibrator != null && mVibrator.hasVibrator();
+    }
+
+    /**
+     * @see Vibrator#cancel()
+     */
+    public void cancel() {
+        if (!hasVibrator()) {
+            return;
+        }
+        mExecutor.execute(mVibrator::cancel);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 0f21a6c..74c97fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -674,6 +674,12 @@
         //  having its summary promoted, regardless of how many children it has
         Set<String> groupsWithChildrenLostToStability =
                 getGroupsWithChildrenLostToStability(shadeList);
+        // Like groups which lost a child to stability, any group which lost a child to promotion
+        //  is exempt from having its summary promoted when it has no attached children.
+        Set<String> groupsWithChildrenLostToPromotionOrStability =
+                getGroupsWithChildrenLostToPromotion(shadeList);
+        groupsWithChildrenLostToPromotionOrStability.addAll(groupsWithChildrenLostToStability);
+
         for (int i = 0; i < shadeList.size(); i++) {
             final ListEntry tle = shadeList.get(i);
 
@@ -683,9 +689,9 @@
                 final boolean hasSummary = group.getSummary() != null;
 
                 if (hasSummary && children.size() == 0) {
-                    if (groupsWithChildrenLostToStability.contains(group.getKey())) {
-                        // This group lost a child on this run to stability, so it is exempt from
-                        //  having its summary promoted to the top level, so prune it.
+                    if (groupsWithChildrenLostToPromotionOrStability.contains(group.getKey())) {
+                        // This group lost a child on this run to promotion or stability, so it is
+                        //  exempt from having its summary promoted to the top level, so prune it.
                         //  It has no children, so it will just vanish.
                         pruneGroupAtIndexAndPromoteAnyChildren(shadeList, group, i);
                     } else {
@@ -826,6 +832,26 @@
     }
 
     /**
+     * Collect the keys of any groups which have already lost a child to a {@link NotifPromoter}
+     * this run.
+     *
+     * These groups will be exempt from appearing without any children.
+     */
+    @NonNull
+    private Set<String> getGroupsWithChildrenLostToPromotion(List<ListEntry> shadeList) {
+        ArraySet<String> groupsWithChildrenLostToPromotion = new ArraySet<>();
+        for (int i = 0; i < shadeList.size(); i++) {
+            final ListEntry tle = shadeList.get(i);
+            if (tle.getAttachState().getPromoter() != null) {
+                // This top-level-entry was part of a group, but was promoted out of it.
+                final String groupKey = tle.getRepresentativeEntry().getSbn().getGroupKey();
+                groupsWithChildrenLostToPromotion.add(groupKey);
+            }
+        }
+        return groupsWithChildrenLostToPromotion;
+    }
+
+    /**
      * If a ListEntry was added to the shade list and then later removed (e.g. because it was a
      * group that was broken up), this method will erase any bookkeeping traces of that addition
      * and/or check that they were already erased.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index 0bf21af..0311324 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -15,9 +15,13 @@
  */
 package com.android.systemui.statusbar.notification.collection.coordinator
 
+import android.app.Notification
+import android.app.Notification.GROUP_ALERT_SUMMARY
+import android.util.ArrayMap
 import android.util.ArraySet
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.statusbar.NotificationRemoteInputManager
+import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.ListEntry
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -29,13 +33,13 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback
 import com.android.systemui.statusbar.notification.collection.render.NodeController
 import com.android.systemui.statusbar.notification.dagger.IncomingHeader
-import com.android.systemui.statusbar.notification.interruption.HeadsUpController
 import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider
 import com.android.systemui.statusbar.notification.stack.BUCKET_HEADS_UP
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
 import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
 
 /**
@@ -54,27 +58,283 @@
  */
 @CoordinatorScope
 class HeadsUpCoordinator @Inject constructor(
+    private val mLogger: HeadsUpCoordinatorLogger,
+    private val mSystemClock: SystemClock,
     private val mHeadsUpManager: HeadsUpManager,
     private val mHeadsUpViewBinder: HeadsUpViewBinder,
     private val mNotificationInterruptStateProvider: NotificationInterruptStateProvider,
     private val mRemoteInputManager: NotificationRemoteInputManager,
     @IncomingHeader private val mIncomingHeaderController: NodeController,
-    @Main private val mExecutor: DelayableExecutor
+    @Main private val mExecutor: DelayableExecutor,
 ) : Coordinator {
+    private val mEntriesBindingUntil = ArrayMap<String, Long>()
     private var mEndLifetimeExtension: OnEndLifetimeExtensionCallback? = null
+    private lateinit var mNotifPipeline: NotifPipeline
+    private var mNow: Long = -1
 
     // notifs we've extended the lifetime for
     private val mNotifsExtendingLifetime = ArraySet<NotificationEntry>()
 
     override fun attach(pipeline: NotifPipeline) {
+        mNotifPipeline = pipeline
         mHeadsUpManager.addListener(mOnHeadsUpChangedListener)
         pipeline.addCollectionListener(mNotifCollectionListener)
+        pipeline.addOnBeforeTransformGroupsListener(::onBeforeTransformGroups)
+        pipeline.addOnBeforeFinalizeFilterListener(::onBeforeFinalizeFilter)
         pipeline.addPromoter(mNotifPromoter)
         pipeline.addNotificationLifetimeExtender(mLifetimeExtender)
     }
 
     private fun onHeadsUpViewBound(entry: NotificationEntry) {
         mHeadsUpManager.showNotification(entry)
+        mEntriesBindingUntil.remove(entry.key)
+    }
+
+    /**
+     * Once the pipeline starts running, we can look through posted entries and quickly process
+     * any that don't have groups, and thus will never gave a group alert edge case.
+     */
+    fun onBeforeTransformGroups(list: List<ListEntry>) {
+        mNow = mSystemClock.currentTimeMillis()
+        if (mPostedEntries.isEmpty()) {
+            return
+        }
+        // Process all non-group adds/updates
+        mPostedEntries.values.toList().forEach { posted ->
+            if (!posted.entry.sbn.isGroup) {
+                handlePostedEntry(posted, "non-group")
+                mPostedEntries.remove(posted.key)
+            }
+        }
+    }
+
+    /**
+     * Once we have a nearly final shade list (not including what's pruned for inflation reasons),
+     * we know that stability and [NotifPromoter]s have been applied, so we can use the location of
+     * notifications in this list to determine what kind of group alert behavior should happen.
+     */
+    fun onBeforeFinalizeFilter(list: List<ListEntry>) {
+        // Nothing to do if there are no other adds/updates
+        if (mPostedEntries.isEmpty()) {
+            return
+        }
+        // Calculate a bunch of information about the logical group and the locations of group
+        // entries in the nearly-finalized shade list.  These may be used in the per-group loop.
+        val postedEntriesByGroup = mPostedEntries.values.groupBy { it.entry.sbn.groupKey }
+        val logicalMembersByGroup = mNotifPipeline.allNotifs.asSequence()
+            .filter { postedEntriesByGroup.contains(it.sbn.groupKey) }
+            .groupBy { it.sbn.groupKey }
+        val groupLocationsByKey: Map<String, GroupLocation> by lazy { getGroupLocationsByKey(list) }
+        mLogger.logEvaluatingGroups(postedEntriesByGroup.size)
+        // For each group, determine which notification(s) for a group should alert.
+        postedEntriesByGroup.forEach { (groupKey, postedEntries) ->
+            // get and classify the logical members
+            val logicalMembers = logicalMembersByGroup[groupKey] ?: emptyList()
+            val logicalSummary = logicalMembers.find { it.sbn.notification.isGroupSummary }
+
+            // Report the start of this group's evaluation
+            mLogger.logEvaluatingGroup(groupKey, postedEntries.size, logicalMembers.size)
+
+            // If there is no logical summary, then there is no alert to transfer
+            if (logicalSummary == null) {
+                postedEntries.forEach { handlePostedEntry(it, "logical-summary-missing") }
+                return@forEach
+            }
+
+            // If summary isn't wanted to be heads up, then there is no alert to transfer
+            if (!isGoingToShowHunStrict(logicalSummary)) {
+                postedEntries.forEach { handlePostedEntry(it, "logical-summary-not-alerting") }
+                return@forEach
+            }
+
+            // The group is alerting! Overall goals:
+            //  - Maybe transfer its alert to a child
+            //  - Also let any/all newly alerting children still alert
+            var childToReceiveParentAlert: NotificationEntry?
+            var targetType = "undefined"
+
+            // If the parent is alerting, always look at the posted notification with the newest
+            // 'when', and if it is isolated with GROUP_ALERT_SUMMARY, then it should receive the
+            // parent's alert.
+            childToReceiveParentAlert =
+                findAlertOverride(postedEntries, groupLocationsByKey::getLocation)
+            if (childToReceiveParentAlert != null) {
+                targetType = "alertOverride"
+            }
+
+            // If the summary is Detached and we have not picked a receiver of the alert, then we
+            // need to look for the best child to alert in place of the summary.
+            val isSummaryAttached = groupLocationsByKey.contains(logicalSummary.key)
+            if (!isSummaryAttached && childToReceiveParentAlert == null) {
+                childToReceiveParentAlert =
+                    findBestTransferChild(logicalMembers, groupLocationsByKey::getLocation)
+                if (childToReceiveParentAlert != null) {
+                    targetType = "bestChild"
+                }
+            }
+
+            // If there is no child to receive the parent alert, then just handle the posted entries
+            // and return.
+            if (childToReceiveParentAlert == null) {
+                postedEntries.forEach { handlePostedEntry(it, "no-transfer-target") }
+                return@forEach
+            }
+
+            // At this point we just need to initiate the transfer
+            val summaryUpdate = mPostedEntries[logicalSummary.key]
+
+            // If the summary was not attached, then remove the alert from the detached summary.
+            // Otherwise we can simply ignore its posted update.
+            if (!isSummaryAttached) {
+                val summaryUpdateForRemoval = summaryUpdate?.also {
+                    it.shouldHeadsUpEver = false
+                } ?: PostedEntry(logicalSummary,
+                    wasAdded = false,
+                    wasUpdated = false,
+                    shouldHeadsUpEver = false,
+                    shouldHeadsUpAgain = false,
+                    isAlerting = mHeadsUpManager.isAlerting(logicalSummary.key),
+                    isBinding = isEntryBinding(logicalSummary),
+                )
+                // If we transfer the alert and the summary isn't even attached, that means we
+                // should ensure the summary is no longer alerting, so we remove it here.
+                handlePostedEntry(summaryUpdateForRemoval, "detached-summary-remove-alert")
+            } else if (summaryUpdate!=null) {
+                mLogger.logPostedEntryWillNotEvaluate(summaryUpdate, "attached-summary-transferred")
+            }
+
+            // Handle all posted entries -- if the child receiving the parent's alert is in the
+            // list, then set its flags to ensure it alerts.
+            var didAlertChildToReceiveParentAlert = false
+            postedEntries.asSequence()
+                .filter { it.key != logicalSummary.key }
+                .forEach { postedEntry ->
+                    if (childToReceiveParentAlert.key == postedEntry.key) {
+                        // Update the child's posted update so that it
+                        postedEntry.shouldHeadsUpEver = true
+                        postedEntry.shouldHeadsUpAgain = true
+                        handlePostedEntry(postedEntry, "child-alert-transfer-target-$targetType")
+                        didAlertChildToReceiveParentAlert = true
+                    } else {
+                        handlePostedEntry(postedEntry, "child-alert-non-target")
+                    }
+                }
+
+            // If the child receiving the alert was not updated on this tick (which can happen in a
+            // standard alert transfer scenario), then construct an update so that we can apply it.
+            if (!didAlertChildToReceiveParentAlert) {
+                val posted = PostedEntry(
+                    childToReceiveParentAlert,
+                    wasAdded = false,
+                    wasUpdated = false,
+                    shouldHeadsUpEver = true,
+                    shouldHeadsUpAgain = true,
+                    isAlerting = mHeadsUpManager.isAlerting(childToReceiveParentAlert.key),
+                    isBinding = isEntryBinding(childToReceiveParentAlert),
+                )
+                handlePostedEntry(posted, "non-posted-child-alert-transfer-target-$targetType")
+            }
+        }
+        // After this method runs, all posted entries should have been handled (or skipped).
+        mPostedEntries.clear()
+    }
+
+    /**
+     * Find the posted child with the newest when, and return it if it is isolated and has
+     * GROUP_ALERT_SUMMARY so that it can be alerted.
+     */
+    private fun findAlertOverride(
+        postedEntries: List<PostedEntry>,
+        locationLookupByKey: (String) -> GroupLocation,
+    ): NotificationEntry? = postedEntries.asSequence()
+        .filter { posted -> !posted.entry.sbn.notification.isGroupSummary }
+        .sortedBy { posted -> -posted.entry.sbn.notification.`when` }
+        .firstOrNull()
+        ?.let { posted ->
+            posted.entry.takeIf { entry ->
+                locationLookupByKey(entry.key) == GroupLocation.Isolated
+                        && entry.sbn.notification.groupAlertBehavior == GROUP_ALERT_SUMMARY
+            }
+        }
+
+    /**
+     * Of children which are attached, look for the child to receive the notification:
+     * First prefer children which were updated, then looking for the ones with the newest 'when'
+     */
+    private fun findBestTransferChild(
+        logicalMembers: List<NotificationEntry>,
+        locationLookupByKey: (String) -> GroupLocation,
+    ): NotificationEntry? = logicalMembers.asSequence()
+        .filter { !it.sbn.notification.isGroupSummary }
+        .filter { locationLookupByKey(it.key) != GroupLocation.Detached }
+        .sortedWith(compareBy(
+            { !mPostedEntries.contains(it.key) },
+            { -it.sbn.notification.`when` },
+        ))
+        .firstOrNull()
+
+    private fun getGroupLocationsByKey(list: List<ListEntry>): Map<String, GroupLocation> =
+        mutableMapOf<String, GroupLocation>().also { map ->
+            list.forEach { topLevelEntry ->
+                when (topLevelEntry) {
+                    is NotificationEntry -> map[topLevelEntry.key] = GroupLocation.Isolated
+                    is GroupEntry -> {
+                        topLevelEntry.summary?.let { summary ->
+                            map[summary.key] = GroupLocation.Summary
+                        }
+                        topLevelEntry.children.forEach { child ->
+                            map[child.key] = GroupLocation.Child
+                        }
+                    }
+                    else -> error("unhandled type $topLevelEntry")
+                }
+            }
+        }
+
+    private val mPostedEntries = LinkedHashMap<String, PostedEntry>()
+
+    fun handlePostedEntry(posted: PostedEntry, scenario: String) {
+        mLogger.logPostedEntryWillEvaluate(posted, scenario)
+        if (posted.wasAdded) {
+            if (posted.shouldHeadsUpEver) {
+                bindForAsyncHeadsUp(posted)
+            }
+        } else {
+            if (posted.isHeadsUpAlready) {
+                // NOTE: This might be because we're alerting (i.e. tracked by HeadsUpManager) OR
+                // it could be because we're binding, and that will affect the next step.
+                if (posted.shouldHeadsUpEver) {
+                    // If alerting, we need to post an update.  Otherwise we're still binding,
+                    // and we can just let that finish.
+                    if (posted.isAlerting) {
+                        mHeadsUpManager.updateNotification(posted.key, posted.shouldHeadsUpAgain)
+                    }
+                } else {
+                    if (posted.isAlerting) {
+                        // We don't want this to be interrupting anymore, let's remove it
+                        mHeadsUpManager.removeNotification(posted.key, false /*removeImmediately*/)
+                    } else {
+                        // Don't let the bind finish
+                        cancelHeadsUpBind(posted.entry)
+                    }
+                }
+            } else if (posted.shouldHeadsUpEver && posted.shouldHeadsUpAgain) {
+                // This notification was updated to be heads up, show it!
+                bindForAsyncHeadsUp(posted)
+            }
+        }
+    }
+
+    private fun cancelHeadsUpBind(entry: NotificationEntry) {
+        mEntriesBindingUntil.remove(entry.key)
+        mHeadsUpViewBinder.abortBindCallback(entry)
+    }
+
+    private fun bindForAsyncHeadsUp(posted: PostedEntry) {
+        // TODO: Add a guarantee to bindHeadsUpView of some kind of callback if the bind is
+        //  cancelled so that we don't need to have this sad timeout hack.
+        mEntriesBindingUntil[posted.key] = mNow + BIND_TIMEOUT
+        mHeadsUpViewBinder.bindHeadsUpView(posted.entry, this::onHeadsUpViewBound)
     }
 
     private val mNotifCollectionListener = object : NotifCollectionListener {
@@ -82,9 +342,17 @@
          * Notification was just added and if it should heads up, bind the view and then show it.
          */
         override fun onEntryAdded(entry: NotificationEntry) {
-            if (mNotificationInterruptStateProvider.shouldHeadsUp(entry)) {
-                mHeadsUpViewBinder.bindHeadsUpView(entry) { entry -> onHeadsUpViewBound(entry) }
-            }
+            // shouldHeadsUp includes check for whether this notification should be filtered
+            val shouldHeadsUpEver = mNotificationInterruptStateProvider.shouldHeadsUp(entry)
+            mPostedEntries[entry.key] = PostedEntry(
+                entry,
+                wasAdded = true,
+                wasUpdated = false,
+                shouldHeadsUpEver = shouldHeadsUpEver,
+                shouldHeadsUpAgain = true,
+                isAlerting = false,
+                isBinding = false,
+            )
         }
 
         /**
@@ -93,22 +361,26 @@
          * up again.
          */
         override fun onEntryUpdated(entry: NotificationEntry) {
-            val hunAgain = HeadsUpController.alertAgain(entry, entry.sbn.notification)
-            // includes check for whether this notification should be filtered:
-            val shouldHeadsUp = mNotificationInterruptStateProvider.shouldHeadsUp(entry)
-            val wasHeadsUp = mHeadsUpManager.isAlerting(entry.key)
-            if (wasHeadsUp) {
-                if (shouldHeadsUp) {
-                    mHeadsUpManager.updateNotification(entry.key, hunAgain)
-                } else {
-                    // We don't want this to be interrupting anymore, let's remove it
-                    mHeadsUpManager.removeNotification(
-                        entry.key, false /* removeImmediately */
-                    )
-                }
-            } else if (shouldHeadsUp && hunAgain) {
-                // This notification was updated to be heads up, show it!
-                mHeadsUpViewBinder.bindHeadsUpView(entry) { entry -> onHeadsUpViewBound(entry) }
+            val shouldHeadsUpEver = mNotificationInterruptStateProvider.shouldHeadsUp(entry)
+            val shouldHeadsUpAgain = shouldHunAgain(entry)
+            val isAlerting = mHeadsUpManager.isAlerting(entry.key)
+            val isBinding = isEntryBinding(entry)
+            mPostedEntries.compute(entry.key) { _, value ->
+                value?.also { update ->
+                    update.wasUpdated = true
+                    update.shouldHeadsUpEver = update.shouldHeadsUpEver || shouldHeadsUpEver
+                    update.shouldHeadsUpAgain = update.shouldHeadsUpAgain || shouldHeadsUpAgain
+                    update.isAlerting = isAlerting
+                    update.isBinding = isBinding
+                } ?: PostedEntry(
+                    entry,
+                    wasAdded = false,
+                    wasUpdated = true,
+                    shouldHeadsUpEver = shouldHeadsUpEver,
+                    shouldHeadsUpAgain = shouldHeadsUpAgain,
+                    isAlerting = isAlerting,
+                    isBinding = isBinding,
+                )
             }
         }
 
@@ -116,8 +388,12 @@
          * Stop alerting HUNs that are removed from the notification collection
          */
         override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
+            mPostedEntries.remove(entry.key)
+            cancelHeadsUpBind(entry)
             val entryKey = entry.key
             if (mHeadsUpManager.isAlerting(entryKey)) {
+                // TODO: This should probably know the RemoteInputCoordinator's conditions,
+                //  or otherwise reference that coordinator's state, rather than replicate its logic
                 val removeImmediatelyForRemoteInput = (mRemoteInputManager.isSpinning(entryKey) &&
                         !NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY)
                 mHeadsUpManager.removeNotification(entry.key, removeImmediatelyForRemoteInput)
@@ -129,6 +405,14 @@
         }
     }
 
+    /**
+     * Checks whether an update for a notification warrants an alert for the user.
+     */
+    private fun shouldHunAgain(entry: NotificationEntry): Boolean {
+        return (!entry.hasInterrupted() ||
+                (entry.sbn.notification.flags and Notification.FLAG_ONLY_ALERT_ONCE) == 0)
+    }
+
     private val mLifetimeExtender = object : NotifLifetimeExtender {
         override fun getName() = TAG
 
@@ -164,11 +448,13 @@
 
     private val mNotifPromoter = object : NotifPromoter(TAG) {
         override fun shouldPromoteToTopLevel(entry: NotificationEntry): Boolean =
-            isCurrentlyShowingHun(entry)
+            isGoingToShowHunNoRetract(entry)
     }
 
     val sectioner = object : NotifSectioner("HeadsUp", BUCKET_HEADS_UP) {
-        override fun isInSection(entry: ListEntry): Boolean = isCurrentlyShowingHun(entry)
+        override fun isInSection(entry: ListEntry): Boolean =
+            // TODO: This check won't notice if a child of the group is going to HUN...
+            isGoingToShowHunNoRetract(entry)
 
         override fun getHeaderNodeController(): NodeController? =
             // TODO: remove SHOW_ALL_SECTIONS, this redundant method, and mIncomingHeaderController
@@ -186,7 +472,34 @@
 
     private fun isSticky(entry: NotificationEntry) = mHeadsUpManager.isSticky(entry.key)
 
-    private fun isCurrentlyShowingHun(entry: ListEntry) = mHeadsUpManager.isAlerting(entry.key)
+    private fun isEntryBinding(entry: ListEntry): Boolean {
+        val bindingUntil = mEntriesBindingUntil[entry.key]
+        return bindingUntil != null && bindingUntil >= mNow
+    }
+
+    /**
+     * Whether the notification is already alerting or binding so that it can imminently alert
+     */
+    private fun isAttemptingToShowHun(entry: ListEntry) =
+        mHeadsUpManager.isAlerting(entry.key) || isEntryBinding(entry)
+
+    /**
+     * Whether the notification is already alerting/binding per [isAttemptingToShowHun] OR if it
+     * has been updated so that it should alert this update.  This method is permissive because it
+     * returns `true` even if the update would (in isolation of its group) cause the alert to be
+     * retracted.  This is important for not retracting transferred group alerts.
+     */
+    private fun isGoingToShowHunNoRetract(entry: ListEntry) =
+        mPostedEntries[entry.key]?.calculateShouldBeHeadsUpNoRetract ?: isAttemptingToShowHun(entry)
+
+    /**
+     * If the notification has been updated, then whether it should HUN in isolation, otherwise
+     * defers to the already alerting/binding state of [isAttemptingToShowHun].  This method is
+     * strict because any update which would revoke the alert supersedes the current
+     * alerting/binding state.
+     */
+    private fun isGoingToShowHunStrict(entry: ListEntry) =
+        mPostedEntries[entry.key]?.calculateShouldBeHeadsUpStrict ?: isAttemptingToShowHun(entry)
 
     private fun endNotifLifetimeExtensionIfExtended(entry: NotificationEntry) {
         if (mNotifsExtendingLifetime.remove(entry)) {
@@ -196,5 +509,29 @@
 
     companion object {
         private const val TAG = "HeadsUpCoordinator"
+        private const val BIND_TIMEOUT = 1000L
     }
-}
\ No newline at end of file
+
+    data class PostedEntry(
+        val entry: NotificationEntry,
+        val wasAdded: Boolean,
+        var wasUpdated: Boolean,
+        var shouldHeadsUpEver: Boolean,
+        var shouldHeadsUpAgain: Boolean,
+        var isAlerting: Boolean,
+        var isBinding: Boolean,
+    ) {
+        val key = entry.key
+        val isHeadsUpAlready: Boolean
+            get() = isAlerting || isBinding
+        val calculateShouldBeHeadsUpStrict: Boolean
+            get() = shouldHeadsUpEver && (wasAdded || shouldHeadsUpAgain || isHeadsUpAlready)
+        val calculateShouldBeHeadsUpNoRetract: Boolean
+            get() = isHeadsUpAlready || (shouldHeadsUpEver && (wasAdded || shouldHeadsUpAgain))
+    }
+}
+
+private enum class GroupLocation { Detached, Isolated, Summary, Child }
+
+private fun Map<String, GroupLocation>.getLocation(key: String): GroupLocation =
+    getOrDefault(key, GroupLocation.Detached)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
new file mode 100644
index 0000000..204a494
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
@@ -0,0 +1,62 @@
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import android.util.Log
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.NotificationHeadsUpLog
+import javax.inject.Inject
+
+private const val TAG = "HeadsUpCoordinator"
+
+class HeadsUpCoordinatorLogger constructor(
+    private val buffer: LogBuffer,
+    private val verbose: Boolean,
+) {
+    @Inject
+    constructor(@NotificationHeadsUpLog buffer: LogBuffer) :
+            this(buffer, Log.isLoggable(TAG, Log.VERBOSE))
+
+    fun logPostedEntryWillEvaluate(posted: HeadsUpCoordinator.PostedEntry, reason: String) {
+        if (!verbose) return
+        buffer.log(TAG, LogLevel.VERBOSE, {
+            str1 = posted.key
+            str2 = reason
+            bool1 = posted.shouldHeadsUpEver
+            bool2 = posted.shouldHeadsUpAgain
+        }, {
+            "will evaluate posted entry $str1:" +
+                    " reason=$str2 shouldHeadsUpEver=$bool1 shouldHeadsUpAgain=$bool2"
+        })
+    }
+
+    fun logPostedEntryWillNotEvaluate(posted: HeadsUpCoordinator.PostedEntry, reason: String) {
+        if (!verbose) return
+        buffer.log(TAG, LogLevel.VERBOSE, {
+            str1 = posted.key
+            str2 = reason
+        }, {
+            "will not evaluate posted entry $str1: reason=$str2"
+        })
+    }
+
+    fun logEvaluatingGroups(numGroups: Int) {
+        if (!verbose) return
+        buffer.log(TAG, LogLevel.VERBOSE, {
+            int1 = numGroups
+        }, {
+            "evaluating groups for alert transfer: $int1"
+        })
+    }
+
+    fun logEvaluatingGroup(groupKey: String, numPostedEntries: Int, logicalGroupSize: Int) {
+        if (!verbose) return
+        buffer.log(TAG, LogLevel.VERBOSE, {
+            str1 = groupKey
+            int1 = numPostedEntries
+            int2 = logicalGroupSize
+        }, {
+            "evaluating group for alert transfer: $str1" +
+                    " numPostedEntries=$int1 logicalGroupSize=$int2"
+        })
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 5d6d0f7..fca2aa1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -162,6 +162,13 @@
         updateBackgroundTint();
     }
 
+    /**
+     * @return The background of this view.
+     */
+    public NotificationBackgroundView getBackgroundNormal() {
+        return mBackgroundNormal;
+    }
+
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
index 0f615aa..c640ab6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
@@ -29,6 +29,7 @@
 
 import com.android.internal.util.ArrayUtils;
 import com.android.systemui.R;
+import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.notification.ExpandAnimationParameters;
 
 /**
@@ -39,15 +40,17 @@
     private final boolean mDontModifyCorners;
     private Drawable mBackground;
     private int mClipTopAmount;
-    private int mActualHeight;
     private int mClipBottomAmount;
     private int mTintColor;
     private final float[] mCornerRadii = new float[8];
     private boolean mBottomIsRounded;
     private int mBackgroundTop;
     private boolean mBottomAmountClips = true;
+    private int mActualHeight = -1;
+    private int mActualWidth = -1;
     private boolean mExpandAnimationRunning;
-    private float mActualWidth;
+    private int mExpandAnimationWidth = -1;
+    private int mExpandAnimationHeight = -1;
     private int mDrawableAlpha = 255;
     private boolean mIsPressedAllowed;
 
@@ -59,11 +62,12 @@
 
     @Override
     protected void onDraw(Canvas canvas) {
-        if (mClipTopAmount + mClipBottomAmount < mActualHeight - mBackgroundTop
+        if (mClipTopAmount + mClipBottomAmount < getActualHeight() - mBackgroundTop
                 || mExpandAnimationRunning) {
             canvas.save();
             if (!mExpandAnimationRunning) {
-                canvas.clipRect(0, mClipTopAmount, getWidth(), mActualHeight - mClipBottomAmount);
+                canvas.clipRect(0, mClipTopAmount, getWidth(),
+                        getActualHeight() - mClipBottomAmount);
             }
             draw(canvas, mBackground);
             canvas.restore();
@@ -73,17 +77,23 @@
     private void draw(Canvas canvas, Drawable drawable) {
         if (drawable != null) {
             int top = mBackgroundTop;
-            int bottom = mActualHeight;
+            int bottom = getActualHeight();
             if (mBottomIsRounded
                     && mBottomAmountClips
                     && !mExpandAnimationRunning) {
                 bottom -= mClipBottomAmount;
             }
-            int left = 0;
-            int right = getWidth();
+            final boolean isRtl = isLayoutRtl();
+            final int width = getWidth();
+            final int actualWidth = getActualWidth();
+
+            int left = isRtl ? width - actualWidth : 0;
+            int right = isRtl ? width : actualWidth;
+
             if (mExpandAnimationRunning) {
-                left = (int) ((getWidth() - mActualWidth) / 2.0f);
-                right = (int) (left + mActualWidth);
+                // Horizontally center this background view inside of the container
+                left = (int) ((width - actualWidth) / 2.0f);
+                right = (int) (left + actualWidth);
             }
             drawable.setBounds(left, top, right, bottom);
             drawable.draw(canvas);
@@ -152,8 +162,26 @@
         invalidate();
     }
 
-    public int getActualHeight() {
-        return mActualHeight;
+    private int getActualHeight() {
+        if (mExpandAnimationRunning && mExpandAnimationHeight > -1) {
+            return mExpandAnimationHeight;
+        } else if (mActualHeight > -1) {
+            return mActualHeight;
+        }
+        return getHeight();
+    }
+
+    public void setActualWidth(int actualWidth) {
+        mActualWidth = actualWidth;
+    }
+
+    private int getActualWidth() {
+        if (mExpandAnimationRunning && mExpandAnimationWidth > -1) {
+            return mExpandAnimationWidth;
+        } else if (mActualWidth > -1) {
+            return mActualWidth;
+        }
+        return getWidth();
     }
 
     public void setClipTopAmount(int clipTopAmount) {
@@ -241,9 +269,9 @@
     }
 
     /** Set the current expand animation size. */
-    public void setExpandAnimationSize(int actualWidth, int actualHeight) {
-        mActualHeight = actualHeight;
-        mActualWidth = actualWidth;
+    public void setExpandAnimationSize(int width, int height) {
+        mExpandAnimationHeight = width;
+        mExpandAnimationWidth = height;
         invalidate();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 7dc2e19..ce3e27c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -565,6 +565,10 @@
         }
     }
 
+    public float getDozeAmount() {
+        return mDozeAmount;
+    }
+
     /**
      * Is the device fully awake, which is different from not tark at all when there are pulsing
      * notifications.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.java
deleted file mode 100644
index bd5b7d7..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.stack;
-
-import android.animation.AnimatorListenerAdapter;
-import android.content.Context;
-import android.util.AttributeSet;
-
-import com.android.systemui.statusbar.notification.row.ExpandableView;
-
-/**
- * Root view to insert Lock screen media controls into the notification stack.
- */
-public class MediaContainerView extends ExpandableView {
-
-    public MediaContainerView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    @Override
-    public long performRemoveAnimation(long duration, long delay, float translationDirection,
-            boolean isHeadsUpAnimation, float endLocation, Runnable onFinishedRunnable,
-            AnimatorListenerAdapter animationListener) {
-        return 0;
-    }
-
-    @Override
-    public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear,
-            Runnable onEnd) {
-        // No animation, it doesn't need it, this would be local
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
new file mode 100644
index 0000000..b8f28b5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
@@ -0,0 +1,84 @@
+/*
+ * 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.notification.stack
+
+import android.animation.AnimatorListenerAdapter
+import android.content.Context
+import android.content.res.Configuration
+import android.graphics.Canvas
+import android.graphics.Path
+import android.graphics.RectF
+import android.util.AttributeSet
+import com.android.systemui.R
+import com.android.systemui.statusbar.notification.row.ExpandableView
+
+/**
+ * Root view to insert Lock screen media controls into the notification stack.
+ */
+class MediaContainerView(context: Context, attrs: AttributeSet?) : ExpandableView(context, attrs) {
+
+    var cornerRadius = 0f
+    var clipHeight = 0
+    var clipRect = RectF()
+    var clipPath = Path()
+
+    init {
+        setWillNotDraw(false) // Run onDraw after invalidate.
+        updateResources()
+    }
+
+    override fun onConfigurationChanged(newConfig: Configuration?) {
+        super.onConfigurationChanged(newConfig)
+        updateResources()
+    }
+
+    private fun updateResources() {
+        cornerRadius = context.resources
+                .getDimensionPixelSize(R.dimen.notification_corner_radius).toFloat()
+    }
+
+    public override fun updateClipping() {
+        if (clipHeight != actualHeight) {
+            clipHeight = actualHeight
+        }
+        invalidate()
+    }
+
+    override fun onDraw(canvas: Canvas) {
+        super.onDraw(canvas)
+
+        val bounds = canvas.clipBounds
+        bounds.bottom = clipHeight
+        clipRect.set(bounds)
+
+        clipPath.reset()
+        clipPath.addRoundRect(clipRect, cornerRadius, cornerRadius, Path.Direction.CW)
+        canvas.clipPath(clipPath)
+    }
+
+
+    override fun performRemoveAnimation(duration: Long, delay: Long, translationDirection: Float,
+                                        isHeadsUpAnimation: Boolean, endLocation: Float,
+                                        onFinishedRunnable: Runnable?,
+                                        animationListener: AnimatorListenerAdapter?): Long {
+        return 0
+    }
+
+    override fun performAddAnimation(delay: Long, duration: Long, isHeadsUpAppear: Boolean,
+                                     onEnd: Runnable?) {
+        // No animation, it doesn't need it, this would be local
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index eff8af0..9f6a81d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -202,6 +202,7 @@
     private int mBottomMargin;
     private int mBottomInset = 0;
     private float mQsExpansionFraction;
+    private final int mSplitShadeMinContentHeight;
 
     /**
      * The algorithm which calculates the properties for our children
@@ -583,6 +584,8 @@
                 .getDefaultColor();
         int minHeight = res.getDimensionPixelSize(R.dimen.notification_min_height);
         int maxHeight = res.getDimensionPixelSize(R.dimen.notification_max_height);
+        mSplitShadeMinContentHeight = res.getDimensionPixelSize(
+                R.dimen.nssl_split_shade_min_content_height);
         mExpandHelper = new ExpandHelper(getContext(), mExpandHelperCallback,
                 minHeight, maxHeight);
         mExpandHelper.setEventSource(this);
@@ -1273,8 +1276,7 @@
      * @param listenerNeedsAnimation does the listener need to animate?
      */
     private void updateStackPosition(boolean listenerNeedsAnimation) {
-        // Consider interpolating from an mExpansionStartY for use on lockscreen and AOD
-        float endTopPosition = mTopPadding + mExtraTopInsetForFullShadeTransition
+        final float endTopPosition = mTopPadding + mExtraTopInsetForFullShadeTransition
                 + mAmbientState.getOverExpansion()
                 - getCurrentOverScrollAmount(false /* top */);
         final float fraction = mAmbientState.getExpansionFraction();
@@ -1284,15 +1286,31 @@
             mOnStackYChanged.accept(listenerNeedsAnimation);
         }
         if (mQsExpansionFraction <= 0) {
-            final float stackEndHeight = Math.max(0f,
-                    getHeight() - getEmptyBottomMargin() - mTopPadding);
-            mAmbientState.setStackEndHeight(stackEndHeight);
-            mAmbientState.setStackHeight(
-                    MathUtils.lerp(stackEndHeight * StackScrollAlgorithm.START_FRACTION,
-                            stackEndHeight, fraction));
+            final float endHeight = updateStackEndHeight(
+                    getHeight(), getEmptyBottomMargin(), mTopPadding);
+            updateStackHeight(endHeight, fraction);
         }
     }
 
+    public float updateStackEndHeight(float height, float bottomMargin, float topPadding) {
+        final float stackEndHeight = Math.max(0f, height - bottomMargin - topPadding);
+        mAmbientState.setStackEndHeight(stackEndHeight);
+        return stackEndHeight;
+    }
+
+    public void updateStackHeight(float endHeight, float fraction) {
+        // During the (AOD<=>LS) transition where dozeAmount is changing,
+        // apply dozeAmount to stack height instead of expansionFraction
+        // to unfurl notifications on AOD=>LS wakeup (and furl up on LS=>AOD sleep)
+        final float dozeAmount = mAmbientState.getDozeAmount();
+        if (0f < dozeAmount && dozeAmount < 1f) {
+            fraction = 1f - dozeAmount;
+        }
+        mAmbientState.setStackHeight(
+                MathUtils.lerp(endHeight * StackScrollAlgorithm.START_FRACTION,
+                        endHeight, fraction));
+    }
+
     /**
      * Add a listener when the StackY changes. The argument signifies whether an animation is
      * needed.
@@ -3910,7 +3928,17 @@
 
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     int getEmptyBottomMargin() {
-        return Math.max(mMaxLayoutHeight - mContentHeight, 0);
+        int contentHeight;
+        if (mShouldUseSplitNotificationShade) {
+            // When in split shade and there are no notifications, the height can be too low, as
+            // it is based on notifications bottom, which is lower on split shade.
+            // Here we prefer to use at least a minimum height defined for split shade.
+            // Otherwise the expansion motion is too fast.
+            contentHeight = Math.max(mSplitShadeMinContentHeight, mContentHeight);
+        } else {
+            contentHeight = mContentHeight;
+        }
+        return Math.max(mMaxLayoutHeight - contentHeight, 0);
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
@@ -5469,6 +5497,17 @@
     }
 
     /**
+     * @param fraction Fraction of the lockscreen to shade transition. 0f for all other states.
+     *                 Once the lockscreen to shade transition completes and the shade is 100% open
+     *                 LockscreenShadeTransitionController resets fraction to 0
+     *                 where it remains until the next lockscreen-to-shade transition.
+     */
+    public void setFractionToShade(float fraction) {
+        mShelf.setFractionToShade(fraction);
+        requestChildrenUpdate();
+    }
+
+    /**
      * Set a listener to when scrolling changes.
      */
     public void setOnScrollListener(Consumer<Integer> listener) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 0d0e5e8..334128a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -1515,10 +1515,18 @@
     }
 
     /**
-     * Set the amount of pixels we have currently dragged down if we're transitioning to the full
-     * shade. 0.0f means we're not transitioning yet.
+     * @param amount The amount of pixels we have currently dragged down
+     *               for the lockscreen to shade transition. 0f for all other states.
+     * @param fraction The fraction of lockscreen to shade transition.
+     *                 0f for all other states.
+     *
+     * Once the lockscreen to shade transition completes and the shade is 100% open,
+     * LockscreenShadeTransitionController resets amount and fraction to 0, where they remain
+     * until the next lockscreen-to-shade transition.
      */
-    public void setTransitionToFullShadeAmount(float amount) {
+    public void setTransitionToFullShadeAmount(float amount, float fraction) {
+        mView.setFractionToShade(fraction);
+
         float extraTopInset = 0.0f;
         if (mStatusBarStateController.getState() == KEYGUARD) {
             float overallProgress = MathUtils.saturate(amount / mView.getHeight());
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 0d2bddc..89b5aef 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
@@ -44,6 +44,7 @@
     public static final int ANIMATION_DURATION_STANDARD = 360;
     public static final int ANIMATION_DURATION_CORNER_RADIUS = 200;
     public static final int ANIMATION_DURATION_WAKEUP = 500;
+    public static final int ANIMATION_DURATION_WAKEUP_SCRIM = 667;
     public static final int ANIMATION_DURATION_GO_TO_FULL_SHADE = 448;
     public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464;
     public static final int ANIMATION_DURATION_SWIPE = 200;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 8d500fa..04d3e9a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static android.app.StatusBarManager.SESSION_KEYGUARD;
+
 import android.annotation.IntDef;
 import android.content.Context;
 import android.content.res.Resources;
@@ -28,7 +30,10 @@
 import android.os.Trace;
 import android.util.Log;
 
+import androidx.annotation.Nullable;
+
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.InstanceId;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
@@ -48,6 +53,7 @@
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationMediaManager;
@@ -156,6 +162,7 @@
     private final DozeParameters mDozeParameters;
     private final KeyguardStateController mKeyguardStateController;
     private final NotificationShadeWindowController mNotificationShadeWindowController;
+    private final SessionTracker mSessionTracker;
     private final Context mContext;
     private final int mWakeUpDelay;
     private int mMode;
@@ -273,7 +280,8 @@
             ScreenLifecycle screenLifecycle,
             AuthController authController,
             StatusBarStateController statusBarStateController,
-            KeyguardUnlockAnimationController keyguardUnlockAnimationController) {
+            KeyguardUnlockAnimationController keyguardUnlockAnimationController,
+            SessionTracker sessionTracker) {
         mContext = context;
         mPowerManager = powerManager;
         mShadeController = shadeController;
@@ -297,6 +305,7 @@
         mAuthController = authController;
         mStatusBarStateController = statusBarStateController;
         mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
+        mSessionTracker = sessionTracker;
         dumpManager.registerDumpable(getClass().getName(), this);
     }
 
@@ -376,7 +385,7 @@
         mMetricsLogger.write(new LogMaker(MetricsEvent.BIOMETRIC_AUTH)
                 .setType(MetricsEvent.TYPE_SUCCESS).setSubtype(toSubtype(biometricSourceType)));
         Optional.ofNullable(BiometricUiEvent.SUCCESS_EVENT_BY_SOURCE_TYPE.get(biometricSourceType))
-                .ifPresent(UI_EVENT_LOGGER::log);
+                .ifPresent(event -> UI_EVENT_LOGGER.log(event, getSessionId()));
 
         boolean unlockAllowed =
                 mKeyguardStateController.isOccluded()
@@ -641,7 +650,7 @@
         mMetricsLogger.write(new LogMaker(MetricsEvent.BIOMETRIC_AUTH)
                 .setType(MetricsEvent.TYPE_FAILURE).setSubtype(toSubtype(biometricSourceType)));
         Optional.ofNullable(BiometricUiEvent.FAILURE_EVENT_BY_SOURCE_TYPE.get(biometricSourceType))
-                .ifPresent(UI_EVENT_LOGGER::log);
+                .ifPresent(event -> UI_EVENT_LOGGER.log(event, getSessionId()));
 
         if (biometricSourceType == BiometricSourceType.FINGERPRINT
                 && mUpdateMonitor.isUdfpsSupported()) {
@@ -656,7 +665,7 @@
 
             if (mNumConsecutiveFpFailures >= FP_ATTEMPTS_BEFORE_SHOW_BOUNCER) {
                 startWakeAndUnlock(MODE_SHOW_BOUNCER);
-                UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN);
+                UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN, getSessionId());
                 mNumConsecutiveFpFailures = 0;
             }
         }
@@ -670,7 +679,7 @@
                 .setType(MetricsEvent.TYPE_ERROR).setSubtype(toSubtype(biometricSourceType))
                 .addTaggedData(MetricsEvent.FIELD_BIOMETRIC_AUTH_ERROR, msgId));
         Optional.ofNullable(BiometricUiEvent.ERROR_EVENT_BY_SOURCE_TYPE.get(biometricSourceType))
-                .ifPresent(UI_EVENT_LOGGER::log);
+                .ifPresent(event -> UI_EVENT_LOGGER.log(event, getSessionId()));
 
         // if we're on the shade and we're locked out, immediately show the bouncer
         if (biometricSourceType == BiometricSourceType.FINGERPRINT
@@ -680,7 +689,7 @@
                 && (mStatusBarStateController.getState() == StatusBarState.SHADE
                     || mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED)) {
             startWakeAndUnlock(MODE_SHOW_BOUNCER);
-            UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN);
+            UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN, getSessionId());
         }
         cleanup();
     }
@@ -786,6 +795,9 @@
         return mBiometricType;
     }
 
+    private @Nullable InstanceId getSessionId() {
+        return mSessionTracker.getSessionId(SESSION_KEYGUARD);
+    }
     /**
      * Translates biometric source type for logging purpose.
      */
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 c09c485..febf2b5 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,8 @@
         }
     }.setDuration(CONTENT_FADE_DURATION);
 
-    private static final int MAX_VISIBLE_ICONS_ON_LOCK = 5;
+    private static final int MAX_ICONS_ON_AOD = 5;
+    public static final int MAX_ICONS_ON_LOCKSCREEN = 3;
     public static final int MAX_STATIC_ICONS = 4;
     private static final int MAX_DOTS = 1;
 
@@ -386,6 +387,19 @@
     }
 
     /**
+     * @return Width of shelf for the given number of icons and overflow dot
+     */
+    public int calculateWidthFor(int numMaxIcons) {
+        if (getChildCount() == 0) {
+            return 0;
+        }
+        return (int) (getActualPaddingStart()
+                + numMaxIcons * mIconSize
+                + mOverflowWidth
+                + getActualPaddingEnd());
+    }
+
+    /**
      * Calculate the horizontal translations for each notification based on how much the icons
      * are inserted into the notification container.
      * If this is not a whole number, the fraction means by how much the icon is appearing.
@@ -394,7 +408,7 @@
         float translationX = getActualPaddingStart();
         int firstOverflowIndex = -1;
         int childCount = getChildCount();
-        int maxVisibleIcons = mOnLockScreen ? MAX_VISIBLE_ICONS_ON_LOCK :
+        int maxVisibleIcons = mOnLockScreen ? MAX_ICONS_ON_AOD :
                 mIsStaticLayout ? MAX_STATIC_ICONS : childCount;
         float layoutEnd = getLayoutEnd();
         float overflowStart = getMaxOverflowStart();
@@ -414,7 +428,7 @@
             }
             boolean forceOverflow = mSpeedBumpIndex != -1 && i >= mSpeedBumpIndex
                     && iconState.iconAppearAmount > 0.0f || i >= maxVisibleIcons;
-            boolean noOverflowAfter = i == childCount - 1;
+            boolean isLastChild = i == childCount - 1;
             float drawingScale = mOnLockScreen && view instanceof StatusBarIconView
                     ? ((StatusBarIconView) view).getIconScaleIncreased()
                     : 1f;
@@ -423,10 +437,10 @@
                     : StatusBarIconView.STATE_ICON;
 
             boolean isOverflowing =
-                    (translationX > (noOverflowAfter ? layoutEnd - mIconSize
+                    (translationX > (isLastChild ? layoutEnd - mIconSize
                             : overflowStart - mIconSize));
             if (firstOverflowIndex == -1 && (forceOverflow || isOverflowing)) {
-                firstOverflowIndex = noOverflowAfter && !forceOverflow ? i - 1 : i;
+                firstOverflowIndex = isLastChild && !forceOverflow ? i - 1 : i;
                 mVisualOverflowStart = layoutEnd - mOverflowWidth;
                 if (forceOverflow || mIsStaticLayout) {
                     mVisualOverflowStart = Math.min(translationX, mVisualOverflowStart);
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 cfa2456..bb0ed95 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -2484,7 +2484,6 @@
         mSplitShadeHeaderController.setShadeExpandedFraction(shadeExpandedFraction);
         mSplitShadeHeaderController.setQsExpandedFraction(qsExpansionFraction);
         mSplitShadeHeaderController.setShadeExpanded(mQsVisible);
-        mKeyguardStatusBarViewController.updateViewState();
 
         if (mCommunalViewController != null) {
             mCommunalViewController.updateQsExpansion(qsExpansionFraction);
@@ -4189,9 +4188,10 @@
                     return false;
                 }
 
-                // Do not allow panel expansion if bouncer is scrimmed, otherwise user would be able
-                // to pull down QS or expand the shade.
-                if (mStatusBar.isBouncerShowingScrimmed()) {
+                // Do not allow panel expansion if bouncer is scrimmed or showing over a dream,
+                // otherwise user would be able to pull down QS or expand the shade.
+                if (mStatusBar.isBouncerShowingScrimmed()
+                        || mStatusBar.isBouncerShowingOverDream()) {
                     return false;
                 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index dc6efba..c466a8c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -73,6 +73,10 @@
 public abstract class PanelViewController {
     public static final boolean DEBUG = PanelView.DEBUG;
     public static final String TAG = PanelView.class.getSimpleName();
+    public static final float FLING_MAX_LENGTH_SECONDS = 0.6f;
+    public static final float FLING_SPEED_UP_FACTOR = 0.6f;
+    public static final float FLING_CLOSING_MAX_LENGTH_SECONDS = 0.6f;
+    public static final float FLING_CLOSING_SPEED_UP_FACTOR = 0.6f;
     private static final int NO_FIXED_DURATION = -1;
     private static final long SHADE_OPEN_SPRING_OUT_DURATION = 350L;
     private static final long SHADE_OPEN_SPRING_BACK_DURATION = 400L;
@@ -269,13 +273,13 @@
         mNotificationShadeWindowController = notificationShadeWindowController;
         mFlingAnimationUtils = flingAnimationUtilsBuilder
                 .reset()
-                .setMaxLengthSeconds(0.6f)
-                .setSpeedUpFactor(0.6f)
+                .setMaxLengthSeconds(FLING_MAX_LENGTH_SECONDS)
+                .setSpeedUpFactor(FLING_SPEED_UP_FACTOR)
                 .build();
         mFlingAnimationUtilsClosing = flingAnimationUtilsBuilder
                 .reset()
-                .setMaxLengthSeconds(0.5f)
-                .setSpeedUpFactor(0.6f)
+                .setMaxLengthSeconds(FLING_CLOSING_MAX_LENGTH_SECONDS)
+                .setSpeedUpFactor(FLING_CLOSING_SPEED_UP_FACTOR)
                 .build();
         mFlingAnimationUtilsDismissing = flingAnimationUtilsBuilder
                 .reset()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index d2e1650..ef5f216 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -62,7 +62,7 @@
         public void prepare(ScrimState previousState) {
             mBlankScreen = false;
             if (previousState == ScrimState.AOD) {
-                mAnimationDuration = StackStateAnimator.ANIMATION_DURATION_WAKEUP;
+                mAnimationDuration = StackStateAnimator.ANIMATION_DURATION_WAKEUP_SCRIM;
                 if (mDisplayRequiresBlanking) {
                     // DisplayPowerManager will blank the screen, we'll just
                     // set our scrim to black in this frame to avoid flickering and
@@ -70,7 +70,7 @@
                     mBlankScreen = true;
                 }
             } else if (previousState == ScrimState.KEYGUARD) {
-                mAnimationDuration = StackStateAnimator.ANIMATION_DURATION_WAKEUP;
+                mAnimationDuration = StackStateAnimator.ANIMATION_DURATION_WAKEUP_SCRIM;
             } else {
                 mAnimationDuration = ScrimController.ANIMATION_DURATION;
             }
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 c09c3ca..a144533 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -150,6 +150,7 @@
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.demomode.DemoMode;
 import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.emergency.EmergencyGesture;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
@@ -338,6 +339,7 @@
     }
 
     private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
+    private final DreamOverlayStateController mDreamOverlayStateController;
     private StatusBarCommandQueueCallbacks mCommandQueueCallbacks;
 
     void onStatusBarWindowStateChanged(@WindowVisibleState int state) {
@@ -781,7 +783,8 @@
             ActivityLaunchAnimator activityLaunchAnimator,
             NotifPipelineFlags notifPipelineFlags,
             InteractionJankMonitor jankMonitor,
-            DeviceStateManager deviceStateManager) {
+            DeviceStateManager deviceStateManager,
+            DreamOverlayStateController dreamOverlayStateController) {
         super(context);
         mNotificationsController = notificationsController;
         mFragmentService = fragmentService;
@@ -869,6 +872,7 @@
         mMessageRouter = messageRouter;
         mWallpaperManager = wallpaperManager;
         mJankMonitor = jankMonitor;
+        mDreamOverlayStateController = dreamOverlayStateController;
 
         mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
         mStartingSurfaceOptional = startingSurfaceOptional;
@@ -4144,6 +4148,10 @@
         return isBouncerShowing() && mStatusBarKeyguardViewManager.bouncerNeedsScrimming();
     }
 
+    public boolean isBouncerShowingOverDream() {
+        return isBouncerShowing() && mDreamOverlayStateController.isOverlayActive();
+    }
+
     /**
      * When {@link KeyguardBouncer} starts to be dismissed, playing its animation.
      */
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 316e682..b203496 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -51,6 +51,7 @@
 import com.android.systemui.DejankUtils;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dock.DockManager;
+import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -111,6 +112,7 @@
     private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final KeyguardBouncer.Factory mKeyguardBouncerFactory;
     private final KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory;
+    private final DreamOverlayStateController mDreamOverlayStateController;
     private KeyguardMessageAreaController mKeyguardMessageAreaController;
     private final Lazy<ShadeController> mShadeController;
     private final BouncerExpansionCallback mExpansionCallback = new BouncerExpansionCallback() {
@@ -235,6 +237,7 @@
             SysuiStatusBarStateController sysuiStatusBarStateController,
             ConfigurationController configurationController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
+            DreamOverlayStateController dreamOverlayStateController,
             NavigationModeController navigationModeController,
             DockManager dockManager,
             NotificationShadeWindowController notificationShadeWindowController,
@@ -249,6 +252,7 @@
         mConfigurationController = configurationController;
         mNavigationModeController = navigationModeController;
         mNotificationShadeWindowController = notificationShadeWindowController;
+        mDreamOverlayStateController = dreamOverlayStateController;
         mKeyguardStateController = keyguardStateController;
         mMediaManager = notificationMediaManager;
         mKeyguardUpdateManager = keyguardUpdateMonitor;
@@ -1174,7 +1178,9 @@
     }
 
     public boolean bouncerNeedsScrimming() {
-        return mOccluded || mBouncer.willDismissWithAction()
+        // When a dream overlay is active, scrimming will cause any expansion to immediately expand.
+        return (mOccluded && !mDreamOverlayStateController.isOverlayActive())
+                || mBouncer.willDismissWithAction()
                 || mStatusBar.isFullScreenUserSwitcherState()
                 || (mBouncer.isShowing() && mBouncer.isScrimmed())
                 || mBouncer.isFullscreenBouncer();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index f5364b9..d3ff4a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -40,6 +40,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
@@ -231,7 +232,8 @@
             ActivityLaunchAnimator activityLaunchAnimator,
             NotifPipelineFlags notifPipelineFlags,
             InteractionJankMonitor jankMonitor,
-            DeviceStateManager deviceStateManager) {
+            DeviceStateManager deviceStateManager,
+            DreamOverlayStateController dreamOverlayStateController) {
         return new StatusBar(
                 context,
                 notificationsController,
@@ -327,7 +329,8 @@
                 activityLaunchAnimator,
                 notifPipelineFlags,
                 jankMonitor,
-                deviceStateManager
+                deviceStateManager,
+                dreamOverlayStateController
         );
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
index e59d2f23..d0fb91c 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
@@ -18,6 +18,7 @@
 
 import com.android.systemui.CoreStartable;
 import com.android.systemui.dagger.GlobalRootComponent;
+import com.android.systemui.statusbar.tv.VpnStatusObserver;
 import com.android.systemui.statusbar.tv.notifications.TvNotificationHandler;
 
 import dagger.Binds;
@@ -34,4 +35,9 @@
     @IntoMap
     @ClassKey(TvNotificationHandler.class)
     CoreStartable bindTvNotificationHandler(TvNotificationHandler systemui);
+
+    @Binds
+    @IntoMap
+    @ClassKey(VpnStatusObserver.class)
+    CoreStartable bindVpnStatusObserver(VpnStatusObserver systemui);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 11725ef..57c7f11 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -33,7 +33,6 @@
 import android.media.AudioSystem;
 import android.media.IAudioService;
 import android.media.IVolumeController;
-import android.media.MediaRoute2Info;
 import android.media.MediaRouter2Manager;
 import android.media.RoutingSessionInfo;
 import android.media.VolumePolicy;
@@ -47,7 +46,6 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.VibrationEffect;
-import android.os.Vibrator;
 import android.provider.Settings;
 import android.service.notification.Condition;
 import android.service.notification.ZenModeConfig;
@@ -68,6 +66,7 @@
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.qs.tiles.DndTile;
+import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.util.RingerModeLiveData;
 import com.android.systemui.util.RingerModeTracker;
 import com.android.systemui.util.concurrency.ThreadFactory;
@@ -78,7 +77,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import java.util.Optional;
 import java.util.concurrent.ConcurrentHashMap;
 
 import javax.inject.Inject;
@@ -135,7 +133,7 @@
     protected C mCallbacks = new C();
     private final State mState = new State();
     protected final MediaSessionsCallbacks mMediaSessionsCallbacksW;
-    private final Optional<Vibrator> mVibrator;
+    private final VibratorHelper mVibrator;
     private final boolean mHasVibrator;
     private boolean mShowA11yStream;
     private boolean mShowVolumeDialog;
@@ -173,7 +171,7 @@
             ThreadFactory theadFactory,
             AudioManager audioManager,
             NotificationManager notificationManager,
-            Optional<Vibrator> optionalVibrator,
+            VibratorHelper vibrator,
             IAudioService iAudioService,
             AccessibilityManager accessibilityManager,
             PackageManager packageManager,
@@ -199,8 +197,8 @@
         mBroadcastDispatcher = broadcastDispatcher;
         mObserver.init();
         mReceiver.init();
-        mVibrator = optionalVibrator;
-        mHasVibrator = mVibrator.isPresent() && mVibrator.get().hasVibrator();
+        mVibrator = vibrator;
+        mHasVibrator = mVibrator.hasVibrator();
         mAudioService = iAudioService;
 
         boolean accessibilityVolumeStreamActive = accessibilityManager
@@ -393,8 +391,7 @@
     }
 
     public void vibrate(VibrationEffect effect) {
-        mVibrator.ifPresent(
-                vibrator -> vibrator.vibrate(effect, SONIFICIATION_VIBRATION_ATTRIBUTES));
+        mVibrator.vibrate(effect, SONIFICIATION_VIBRATION_ATTRIBUTES);
     }
 
     public boolean hasVibrator() {
@@ -402,7 +399,7 @@
     }
 
     private void onNotifyVisibleW(boolean visible) {
-        if (mDestroyed) return; 
+        if (mDestroyed) return;
         mAudio.notifyVolumeControllerVisible(mVolumeController, visible);
         if (!visible) {
             if (updateActiveStreamW(-1)) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt
index 6bc6505..aa671d1 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt
@@ -20,11 +20,12 @@
 import org.mockito.Mockito.verify
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
+import com.android.internal.logging.InstanceId
 import com.android.internal.logging.UiEventLogger
 import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT
 import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.SessionTracker
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -44,9 +45,11 @@
     @Mock
     lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock
-    lateinit var dumpManager: DumpManager
-    @Mock
     lateinit var strongAuthTracker: KeyguardUpdateMonitor.StrongAuthTracker
+    @Mock
+    lateinit var sessionTracker: SessionTracker
+    @Mock
+    lateinit var sessionId: InstanceId
 
     @Captor
     lateinit var updateMonitorCallbackCaptor: ArgumentCaptor<KeyguardUpdateMonitorCallback>
@@ -58,11 +61,12 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         whenever(keyguardUpdateMonitor.strongAuthTracker).thenReturn(strongAuthTracker)
+        whenever(sessionTracker.getSessionId(anyInt())).thenReturn(sessionId)
         keyguardBiometricLockoutLogger = KeyguardBiometricLockoutLogger(
                 mContext,
                 uiEventLogger,
                 keyguardUpdateMonitor,
-                dumpManager)
+                sessionTracker)
     }
 
     @Test
@@ -76,7 +80,7 @@
 
         // THEN encrypted / lockdown state is logged
         verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
-                .PRIMARY_AUTH_REQUIRED_ENCRYPTED_OR_LOCKDOWN)
+                .PRIMARY_AUTH_REQUIRED_ENCRYPTED_OR_LOCKDOWN, sessionId)
     }
 
     @Test
@@ -93,7 +97,7 @@
 
         // THEN primary auth required state is logged
         verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
-                .PRIMARY_AUTH_REQUIRED_TIMEOUT)
+                .PRIMARY_AUTH_REQUIRED_TIMEOUT, sessionId)
     }
 
     @Test
@@ -110,7 +114,7 @@
 
         // THEN primary auth required state is logged
         verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
-                .PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE)
+                .PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE, sessionId)
     }
 
     @Test
@@ -128,9 +132,9 @@
 
         // THEN primary auth required state is logged with all the reasons
         verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
-                .PRIMARY_AUTH_REQUIRED_TIMEOUT)
+                .PRIMARY_AUTH_REQUIRED_TIMEOUT, sessionId)
         verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
-                .PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE)
+                .PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE, sessionId)
 
         // WHEN onStrongAuthStateChanged is called again
         updateMonitorCallback.onStrongAuthStateChanged(0)
@@ -152,7 +156,7 @@
 
         // THEN primary auth required state is logged
         verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
-                .PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT)
+                .PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT, sessionId)
 
         // WHEN face lockout is reset
         whenever(keyguardUpdateMonitor.isFaceLockedOut).thenReturn(false)
@@ -160,7 +164,7 @@
 
         // THEN primary auth required state is logged
         verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
-                .PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT_RESET)
+                .PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT_RESET, sessionId)
     }
 
     @Test
@@ -176,7 +180,7 @@
 
         // THEN primary auth required state is logged
         verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
-                .PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT)
+                .PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT, sessionId)
 
         // WHEN fingerprint lockout is reset
         whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false)
@@ -184,7 +188,7 @@
 
         // THEN primary auth required state is logged
         verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
-                .PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT_RESET)
+                .PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT_RESET, sessionId)
     }
 
     fun captureUpdateMonitorCallback() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 599e547..a819a7a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -49,6 +49,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -120,6 +121,8 @@
     private FeatureFlags mFeatureFlags;
     @Mock
     private UserSwitcherController mUserSwitcherController;
+    @Mock
+    private SessionTracker mSessionTracker;
     private Configuration mConfiguration;
 
     private KeyguardSecurityContainerController mKeyguardSecurityContainerController;
@@ -154,7 +157,8 @@
                 mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger,
                 mKeyguardStateController, mKeyguardSecurityViewFlipperController,
                 mConfigurationController, mFalsingCollector, mFalsingManager,
-                mUserSwitcherController, mFeatureFlags, mGlobalSettings).create(mSecurityCallback);
+                mUserSwitcherController, mFeatureFlags, mGlobalSettings,
+                mSessionTracker).create(mSecurityCallback);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 5d39eef..c37e966 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -27,10 +27,12 @@
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -46,6 +48,7 @@
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricPrompt;
 import android.hardware.biometrics.ComponentInfoInternal;
+import android.hardware.biometrics.IBiometricContextListener;
 import android.hardware.biometrics.IBiometricSysuiReceiver;
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.biometrics.SensorProperties;
@@ -70,6 +73,7 @@
 import com.android.internal.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.util.concurrency.Execution;
 import com.android.systemui.util.concurrency.FakeExecution;
@@ -80,6 +84,7 @@
 import org.mockito.AdditionalMatchers;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -99,6 +104,8 @@
     @Mock
     private IBiometricSysuiReceiver mReceiver;
     @Mock
+    private IBiometricContextListener mContextListener;
+    @Mock
     private AuthDialog mDialog1;
     @Mock
     private AuthDialog mDialog2;
@@ -120,10 +127,14 @@
     private DisplayManager mDisplayManager;
     @Mock
     private WakefulnessLifecycle mWakefulnessLifecycle;
+    @Mock
+    private StatusBarStateController mStatusBarStateController;
     @Captor
     ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mAuthenticatorsRegisteredCaptor;
     @Captor
     ArgumentCaptor<FingerprintStateListener> mFingerprintStateCaptor;
+    @Captor
+    ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor;
 
     private TestableContext mContextSpy;
     private Execution mExecution;
@@ -175,12 +186,15 @@
 
         mAuthController = new TestableAuthController(mContextSpy, mExecution, mCommandQueue,
                 mActivityTaskManager, mWindowManager, mFingerprintManager, mFaceManager,
-                () -> mUdfpsController, () -> mSidefpsController);
+                () -> mUdfpsController, () -> mSidefpsController, mStatusBarStateController);
 
         mAuthController.start();
         verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
                 mAuthenticatorsRegisteredCaptor.capture());
 
+        when(mStatusBarStateController.isDozing()).thenReturn(false);
+        verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture());
+
         mAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(props);
 
         // Ensures that the operations posted on the handler get executed.
@@ -198,7 +212,8 @@
         // This test requires an uninitialized AuthController.
         AuthController authController = new TestableAuthController(mContextSpy, mExecution,
                 mCommandQueue, mActivityTaskManager, mWindowManager, mFingerprintManager,
-                mFaceManager, () -> mUdfpsController, () -> mSidefpsController);
+                mFaceManager, () -> mUdfpsController, () -> mSidefpsController,
+                mStatusBarStateController);
         authController.start();
 
         verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
@@ -221,7 +236,8 @@
         // This test requires an uninitialized AuthController.
         AuthController authController = new TestableAuthController(mContextSpy, mExecution,
                 mCommandQueue, mActivityTaskManager, mWindowManager, mFingerprintManager,
-                mFaceManager, () -> mUdfpsController, () -> mSidefpsController);
+                mFaceManager, () -> mUdfpsController, () -> mSidefpsController,
+                mStatusBarStateController);
         authController.start();
 
         verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
@@ -656,6 +672,19 @@
         verify(callback).onBiometricPromptDismissed();
     }
 
+    @Test
+    public void testForwardsDozeEvent() throws RemoteException {
+        mAuthController.setBiometicContextListener(mContextListener);
+
+        mStatusBarStateListenerCaptor.getValue().onDozingChanged(false);
+        mStatusBarStateListenerCaptor.getValue().onDozingChanged(true);
+
+        InOrder order = inOrder(mContextListener);
+        // invoked twice since the initial state is false
+        order.verify(mContextListener, times(2)).onDozeChanged(eq(false));
+        order.verify(mContextListener).onDozeChanged(eq(true));
+    }
+
     // Helpers
 
     private void showDialog(int[] sensorIds, boolean credentialAllowed) {
@@ -705,10 +734,12 @@
                 FingerprintManager fingerprintManager,
                 FaceManager faceManager,
                 Provider<UdfpsController> udfpsControllerFactory,
-                Provider<SidefpsController> sidefpsControllerFactory) {
+                Provider<SidefpsController> sidefpsControllerFactory,
+                StatusBarStateController statusBarStateController) {
             super(context, execution, commandQueue, activityTaskManager, windowManager,
                     fingerprintManager, faceManager, udfpsControllerFactory,
-                    sidefpsControllerFactory, mDisplayManager, mWakefulnessLifecycle, mHandler);
+                    sidefpsControllerFactory, mDisplayManager, mWakefulnessLifecycle,
+                    statusBarStateController, mHandler);
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 159bdba..35e838b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -44,7 +44,6 @@
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.VibrationAttributes;
-import android.os.Vibrator;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.LayoutInflater;
@@ -64,6 +63,7 @@
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
+import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -141,7 +141,7 @@
     @Mock
     private ScreenLifecycle mScreenLifecycle;
     @Mock
-    private Vibrator mVibrator;
+    private VibratorHelper mVibrator;
     @Mock
     private UdfpsHapticsSimulator mUdfpsHapticsSimulator;
     @Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
index 55509d1..9908d44 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
@@ -20,13 +20,11 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.controls.ControlsMetricsLogger
-import com.android.systemui.globalactions.GlobalActionsComponent
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.wm.shell.TaskViewFactory
-import dagger.Lazy
-import java.util.Optional
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -41,15 +39,14 @@
 import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
+import java.util.Optional
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 class ControlActionCoordinatorImplTest : SysuiTestCase() {
 
     @Mock
-    private lateinit var uiController: ControlsUiController
-    @Mock
-    private lateinit var lazyUiController: Lazy<ControlsUiController>
+    private lateinit var vibratorHelper: VibratorHelper
     @Mock
     private lateinit var keyguardStateController: KeyguardStateController
     @Mock
@@ -59,8 +56,6 @@
     @Mock
     private lateinit var activityStarter: ActivityStarter
     @Mock
-    private lateinit var globalActionsComponent: GlobalActionsComponent
-    @Mock
     private lateinit var taskViewFactory: Optional<TaskViewFactory>
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private lateinit var cvh: ControlViewHolder
@@ -86,11 +81,9 @@
             uiExecutor,
             activityStarter,
             keyguardStateController,
-            globalActionsComponent,
             taskViewFactory,
-            getFakeBroadcastDispatcher(),
-            lazyUiController,
-            metricsLogger
+            metricsLogger,
+            vibratorHelper
         ))
 
         `when`(cvh.cws.ci.controlId).thenReturn(ID)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index b3b5fa5..d5bd67a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -91,6 +91,9 @@
     @Mock
     DreamOverlayTouchMonitor mDreamOverlayTouchMonitor;
 
+    @Mock
+    DreamOverlayStateController mStateController;
+
 
     DreamOverlayService mService;
 
@@ -115,6 +118,7 @@
 
         mService = new DreamOverlayService(mContext, mMainExecutor,
                 mDreamOverlayComponentFactory,
+                mStateController,
                 mKeyguardUpdateMonitor);
         final IBinder proxy = mService.onBind(new Intent());
         final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
index 7d0833d..1859569 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -16,9 +16,12 @@
 
 package com.android.systemui.dreams;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
@@ -35,6 +38,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 import java.util.Collection;
@@ -56,7 +60,29 @@
     }
 
     @Test
-    public void testCallback() throws Exception {
+    public void testStateChange() {
+        final DreamOverlayStateController stateController = new DreamOverlayStateController(
+                mExecutor);
+        stateController.addCallback(mCallback);
+        stateController.setOverlayActive(true);
+        mExecutor.runAllReady();
+
+        verify(mCallback).onStateChanged();
+        assertThat(stateController.isOverlayActive()).isTrue();
+
+        Mockito.clearInvocations(mCallback);
+        stateController.setOverlayActive(true);
+        mExecutor.runAllReady();
+        verify(mCallback, never()).onStateChanged();
+
+        stateController.setOverlayActive(false);
+        mExecutor.runAllReady();
+        verify(mCallback).onStateChanged();
+        assertThat(stateController.isOverlayActive()).isFalse();
+    }
+
+    @Test
+    public void testCallback() {
         final DreamOverlayStateController stateController = new DreamOverlayStateController(
                 mExecutor);
         stateController.addCallback(mCallback);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
index f227a9b..d5ab708 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
@@ -27,6 +27,7 @@
 import androidx.constraintlayout.widget.ConstraintLayout;
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 
 import org.junit.Before;
@@ -122,6 +123,34 @@
     }
 
     /**
+     * Makes sure the engine properly places a view within the {@link ConstraintLayout}.
+     */
+    @Test
+    public void testSnapToGuide() {
+        final ViewInfo firstViewInfo = new ViewInfo(
+                new ComplicationLayoutParams(
+                        100,
+                        100,
+                        ComplicationLayoutParams.POSITION_TOP
+                                | ComplicationLayoutParams.POSITION_END,
+                        ComplicationLayoutParams.DIRECTION_DOWN,
+                        0,
+                        true),
+                Complication.CATEGORY_STANDARD,
+                mLayout);
+
+        final ComplicationLayoutEngine engine = new ComplicationLayoutEngine(mLayout);
+        addComplication(engine, firstViewInfo);
+
+        // Ensure the view is added to the top end corner
+        verifyChange(firstViewInfo, true, lp -> {
+            assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+            assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+            assertThat(lp.startToEnd == R.id.complication_end_guide).isTrue();
+        });
+    }
+
+    /**
      * Ensures layout in a particular direction updates.
      */
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java
new file mode 100644
index 0000000..f1978b2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.dreams.complication;
+
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_AIR_QUALITY;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_CAST_INFO;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_DATE;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_TIME;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_WEATHER;
+import static com.android.systemui.dreams.complication.ComplicationUtils.convertComplicationType;
+
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.settingslib.dream.DreamBackend;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ComplicationUtilsTest extends SysuiTestCase {
+    @Test
+    public void testConvertComplicationType() {
+        assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_TIME))
+                .isEqualTo(COMPLICATION_TYPE_TIME);
+        assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_DATE))
+                .isEqualTo(COMPLICATION_TYPE_DATE);
+        assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_WEATHER))
+                .isEqualTo(COMPLICATION_TYPE_WEATHER);
+        assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_AIR_QUALITY))
+                .isEqualTo(COMPLICATION_TYPE_AIR_QUALITY);
+        assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_CAST_INFO))
+                .isEqualTo(COMPLICATION_TYPE_CAST_INFO);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
new file mode 100644
index 0000000..1a8326f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
@@ -0,0 +1,252 @@
+/*
+ * 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.dreams.touch;
+
+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.anyFloat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.animation.ValueAnimator;
+import android.testing.AndroidTestingRunner;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.shared.system.InputChannelCompat;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.KeyguardBouncer;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.wm.shell.animation.FlingAnimationUtils;
+
+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;
+
+import java.util.Random;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
+    @Mock
+    StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+
+    @Mock
+    StatusBar mStatusBar;
+
+    @Mock
+    NotificationShadeWindowController mNotificationShadeWindowController;
+
+    @Mock
+    FlingAnimationUtils mFlingAnimationUtils;
+
+
+    @Mock
+    FlingAnimationUtils mFlingAnimationUtilsClosing;
+
+    @Mock
+    DreamTouchHandler.TouchSession mTouchSession;
+
+    BouncerSwipeTouchHandler mTouchHandler;
+
+    @Mock
+    BouncerSwipeTouchHandler.ValueAnimatorCreator mValueAnimatorCreator;
+
+    @Mock
+    ValueAnimator mValueAnimator;
+
+    @Mock
+    BouncerSwipeTouchHandler.VelocityTrackerFactory mVelocityTrackerFactory;
+
+    @Mock
+    VelocityTracker mVelocityTracker;
+
+    private static final float TOUCH_REGION = .3f;
+    private static final float SCREEN_HEIGHT_PX = 100;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mTouchHandler = new BouncerSwipeTouchHandler(
+                mStatusBarKeyguardViewManager,
+                mStatusBar,
+                mNotificationShadeWindowController,
+                mValueAnimatorCreator,
+                mVelocityTrackerFactory,
+                mFlingAnimationUtils,
+                mFlingAnimationUtilsClosing,
+                TOUCH_REGION);
+        when(mStatusBar.getDisplayHeight()).thenReturn(SCREEN_HEIGHT_PX);
+        when(mValueAnimatorCreator.create(anyFloat(), anyFloat())).thenReturn(mValueAnimator);
+        when(mVelocityTrackerFactory.obtain()).thenReturn(mVelocityTracker);
+    }
+
+    private static void beginValidSwipe(GestureDetector.OnGestureListener listener) {
+        listener.onDown(MotionEvent.obtain(0, 0,
+                MotionEvent.ACTION_DOWN, 0,
+                SCREEN_HEIGHT_PX - (.5f * TOUCH_REGION * SCREEN_HEIGHT_PX), 0));
+    }
+
+    /**
+     * Ensures expansion only happens when touch down happens in valid part of the screen.
+     */
+    @Test
+    public void testSessionStart() {
+        mTouchHandler.onSessionStart(mTouchSession);
+        verify(mNotificationShadeWindowController).setForcePluginOpen(eq(true), any());
+        ArgumentCaptor<InputChannelCompat.InputEventListener> eventListenerCaptor =
+                ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class);
+        ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
+                ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
+        verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
+        verify(mTouchSession).registerInputListener(eventListenerCaptor.capture());
+
+        final Random random = new Random(System.currentTimeMillis());
+
+        // If an initial touch down meeting criteria has been met, scroll behavior should be
+        // ignored.
+        assertThat(gestureListenerCaptor.getValue()
+                .onScroll(Mockito.mock(MotionEvent.class),
+                        Mockito.mock(MotionEvent.class),
+                        random.nextFloat(),
+                        random.nextFloat())).isFalse();
+
+        // A touch at the top of the screen should also not trigger listening.
+        final MotionEvent touchDownEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN,
+                0, 0, 0);
+
+        gestureListenerCaptor.getValue().onDown(touchDownEvent);
+        assertThat(gestureListenerCaptor.getValue()
+                .onScroll(Mockito.mock(MotionEvent.class),
+                        Mockito.mock(MotionEvent.class),
+                        random.nextFloat(),
+                        random.nextFloat())).isFalse();
+
+        // A touch within range at the bottom of the screen should trigger listening
+        beginValidSwipe(gestureListenerCaptor.getValue());
+        assertThat(gestureListenerCaptor.getValue()
+                .onScroll(Mockito.mock(MotionEvent.class),
+                        Mockito.mock(MotionEvent.class),
+                        random.nextFloat(),
+                        random.nextFloat())).isTrue();
+    }
+
+    /**
+     * Makes sure expansion amount is proportional to scroll.
+     */
+    @Test
+    public void testExpansionAmount() {
+        mTouchHandler.onSessionStart(mTouchSession);
+        ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
+                ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
+        verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
+
+        beginValidSwipe(gestureListenerCaptor.getValue());
+
+        final float scrollAmount = .3f;
+        final float distanceY = SCREEN_HEIGHT_PX * scrollAmount;
+
+        final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
+                0, SCREEN_HEIGHT_PX, 0);
+        final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
+                0, SCREEN_HEIGHT_PX  - distanceY, 0);
+
+        assertThat(gestureListenerCaptor.getValue().onScroll(event1, event2, 0 , distanceY))
+                .isTrue();
+
+        // Ensure only called once
+        verify(mStatusBarKeyguardViewManager)
+                .onPanelExpansionChanged(anyFloat(), anyBoolean(), anyBoolean());
+
+        // Ensure correct expansion passed in.
+        verify(mStatusBarKeyguardViewManager)
+                .onPanelExpansionChanged(eq(1 - scrollAmount), eq(false), eq(true));
+    }
+
+    private void swipeToPosition(float position, float velocityY) {
+        mTouchHandler.onSessionStart(mTouchSession);
+        ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
+                ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
+        ArgumentCaptor<InputChannelCompat.InputEventListener> inputEventListenerCaptor =
+                ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class);
+        verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
+        verify(mTouchSession).registerInputListener(inputEventListenerCaptor.capture());
+
+        when(mVelocityTracker.getYVelocity()).thenReturn(velocityY);
+
+        beginValidSwipe(gestureListenerCaptor.getValue());
+
+        final float distanceY = SCREEN_HEIGHT_PX * position;
+
+        final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
+                0, SCREEN_HEIGHT_PX, 0);
+        final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
+                0, SCREEN_HEIGHT_PX  - distanceY, 0);
+
+        assertThat(gestureListenerCaptor.getValue().onScroll(event1, event2, 0 , distanceY))
+                .isTrue();
+
+        final MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP,
+                0, 0, 0);
+
+        inputEventListenerCaptor.getValue().onInputEvent(upEvent);
+    }
+
+    /**
+     * Tests that ending a swipe before the set expansion threshold leads to bouncer collapsing
+     * down.
+     */
+    @Test
+    public void testCollapseOnThreshold() {
+        final float swipeUpPercentage = .3f;
+        swipeToPosition(swipeUpPercentage, -1);
+
+        verify(mValueAnimatorCreator).create(eq(1 - swipeUpPercentage),
+                eq(KeyguardBouncer.EXPANSION_VISIBLE));
+        verify(mFlingAnimationUtilsClosing).apply(eq(mValueAnimator), anyFloat(), anyFloat(),
+                anyFloat(), anyFloat());
+        verify(mValueAnimator).start();
+    }
+
+    /**
+     * Tests that ending a swipe above the set expansion threshold will continue the expansion.
+     */
+    @Test
+    public void testExpandOnThreshold() {
+        final float swipeUpPercentage = .7f;
+        swipeToPosition(swipeUpPercentage, 1);
+
+        verify(mValueAnimatorCreator).create(eq(1 - swipeUpPercentage),
+                eq(KeyguardBouncer.EXPANSION_HIDDEN));
+        verify(mFlingAnimationUtils).apply(eq(mValueAnimator), anyFloat(), anyFloat(),
+                anyFloat(), anyFloat());
+        verify(mValueAnimator).start();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt
new file mode 100644
index 0000000..2cb1939
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.dump
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogcatEchoTracker
+
+/**
+ * Creates a LogBuffer that will echo everything to logcat, which is useful for debugging tests.
+ */
+fun logcatLogBuffer(name: String = "EchoToLogcatLogBuffer") =
+    LogBuffer(name, 50, 50, LogcatEchoTrackerAlways())
+
+/**
+ * A [LogcatEchoTracker] that always allows echoing to the logcat.
+ */
+class LogcatEchoTrackerAlways : LogcatEchoTracker {
+    override fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean = true
+    override fun isTagLoggable(tagName: String, level: LogLevel): Boolean = true
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index e3a7e3b..71fc8ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -61,6 +61,7 @@
 import com.android.systemui.plugins.GlobalActions;
 import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -115,6 +116,7 @@
     @Mock private PackageManager mPackageManager;
     @Mock private Handler mHandler;
     @Mock private UserContextProvider mUserContextProvider;
+    @Mock private VibratorHelper mVibratorHelper;
     @Mock private StatusBar mStatusBar;
     @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @Mock private DialogLaunchAnimator mDialogLaunchAnimator;
@@ -143,7 +145,7 @@
                 mTelephonyListenerManager,
                 mGlobalSettings,
                 mSecureSettings,
-                null,
+                mVibratorHelper,
                 mResources,
                 mConfigurationController,
                 mKeyguardStateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index da8ab27..d94e2ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -48,6 +48,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.classifier.FalsingCollectorFake;
+import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
@@ -98,6 +99,7 @@
     private @Mock InteractionJankMonitor mInteractionJankMonitor;
     private @Mock ScreenOnCoordinator mScreenOnCoordinator;
     private @Mock Lazy<NotificationShadeWindowController> mNotificationShadeWindowControllerLazy;
+    private @Mock DreamOverlayStateController mDreamOverlayStateController;
     private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake();
     private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
 
@@ -202,6 +204,7 @@
                 () -> mNotificationShadeDepthController,
                 mScreenOnCoordinator,
                 mInteractionJankMonitor,
+                mDreamOverlayStateController,
                 mNotificationShadeWindowControllerLazy);
         mViewMediator.start();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
index d7c00fb..5ed1d65 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
@@ -38,7 +38,6 @@
 import android.hardware.biometrics.BiometricSourceType;
 import android.hardware.biometrics.SensorLocationInternal;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.os.Vibrator;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.Pair;
@@ -62,6 +61,7 @@
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.concurrency.FakeExecutor;
@@ -104,7 +104,7 @@
     private @Mock DumpManager mDumpManager;
     private @Mock AccessibilityManager mAccessibilityManager;
     private @Mock ConfigurationController mConfigurationController;
-    private @Mock Vibrator mVibrator;
+    private @Mock VibratorHelper mVibrator;
     private @Mock AuthRippleController mAuthRippleController;
     private FakeExecutor mDelayableExecutor = new FakeExecutor(new FakeSystemClock());
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index 42647f7..d51d370 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -227,7 +227,7 @@
     fun testDragDownAmountDoesntCallOutInLockedDownShade() {
         whenever(nsslController.isInLockedDownShade).thenReturn(true)
         transitionController.dragDownAmount = 10f
-        verify(nsslController, never()).setTransitionToFullShadeAmount(anyFloat())
+        verify(nsslController, never()).setTransitionToFullShadeAmount(anyFloat(), anyFloat())
         verify(mediaHierarchyManager, never()).setTransitionToFullShadeAmount(anyFloat())
         verify(scrimController, never()).setTransitionToFullShadeProgress(anyFloat())
         verify(notificationPanelController, never()).setTransitionToFullShadeAmount(anyFloat(),
@@ -238,7 +238,7 @@
     @Test
     fun testDragDownAmountCallsOut() {
         transitionController.dragDownAmount = 10f
-        verify(nsslController).setTransitionToFullShadeAmount(anyFloat())
+        verify(nsslController).setTransitionToFullShadeAmount(anyFloat(), anyFloat())
         verify(mediaHierarchyManager).setTransitionToFullShadeAmount(anyFloat())
         verify(scrimController).setTransitionToFullShadeProgress(anyFloat())
         verify(notificationPanelController).setTransitionToFullShadeAmount(anyFloat(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt
new file mode 100644
index 0000000..ad908e7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt
@@ -0,0 +1,93 @@
+package com.android.systemui.statusbar
+
+import android.media.AudioAttributes
+import android.os.UserHandle
+import android.os.VibrationAttributes
+import android.os.VibrationEffect
+import android.os.Vibrator
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.eq
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+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.`when` as whenever
+import org.mockito.Mockito.any
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import java.util.concurrent.Executor
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class VibratorHelperTest : SysuiTestCase() {
+
+    @JvmField @Rule
+    var rule = MockitoJUnit.rule()
+
+    @Mock lateinit var vibrator: Vibrator
+    @Mock lateinit var executor: Executor
+    @Captor lateinit var backgroundTaskCaptor: ArgumentCaptor<Runnable>
+    lateinit var vibratorHelper: VibratorHelper
+
+    @Before
+    fun setup() {
+        vibratorHelper = VibratorHelper(vibrator, executor)
+        whenever(vibrator.hasVibrator()).thenReturn(true)
+    }
+
+    @Test
+    fun testVibrate() {
+        vibratorHelper.vibrate(VibrationEffect.EFFECT_CLICK)
+        verifyAsync().vibrate(any(VibrationEffect::class.java),
+                any(VibrationAttributes::class.java))
+    }
+
+    @Test
+    fun testVibrate2() {
+        vibratorHelper.vibrate(UserHandle.USER_CURRENT, "package",
+                mock(VibrationEffect::class.java), "reason",
+                mock(VibrationAttributes::class.java))
+        verifyAsync().vibrate(eq(UserHandle.USER_CURRENT), eq("package"),
+                any(VibrationEffect::class.java), eq("reason"),
+                any(VibrationAttributes::class.java))
+    }
+
+    @Test
+    fun testVibrate3() {
+        vibratorHelper.vibrate(mock(VibrationEffect::class.java), mock(AudioAttributes::class.java))
+        verifyAsync().vibrate(any(VibrationEffect::class.java), any(AudioAttributes::class.java))
+    }
+
+    @Test
+    fun testVibrate4() {
+        vibratorHelper.vibrate(mock(VibrationEffect::class.java))
+        verifyAsync().vibrate(any(VibrationEffect::class.java))
+    }
+
+    @Test
+    fun testHasVibrator() {
+        assertThat(vibratorHelper.hasVibrator()).isTrue()
+        verify(vibrator).hasVibrator()
+    }
+
+    @Test
+    fun testCancel() {
+        vibratorHelper.cancel()
+        verifyAsync().cancel()
+    }
+
+    private fun verifyAsync(): Vibrator {
+        verify(executor).execute(backgroundTaskCaptor.capture())
+        verify(vibrator).hasVibrator()
+        backgroundTaskCaptor.value.run()
+
+        return verify(vibrator)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index 2e84482..8fb066b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -368,6 +368,50 @@
     }
 
     @Test
+    public void testGroupsWhoLoseAllChildrenToPromotionSuppressSummary() {
+        // GIVEN a group with two children
+        addGroupChild(0, PACKAGE_2, GROUP_1);
+        addGroupSummary(1, PACKAGE_2, GROUP_1);
+        addGroupChild(2, PACKAGE_2, GROUP_1);
+
+        // GIVEN a promoter that will promote one of children to top level
+        mListBuilder.addPromoter(new IdPromoter(0, 2));
+
+        // WHEN we build the list
+        dispatchBuild();
+
+        // THEN both children end up at top level (because group is now too small)
+        verifyBuiltList(
+                notif(0),
+                notif(2)
+        );
+
+        // THEN the summary is discarded
+        assertNull(mEntrySet.get(1).getParent());
+    }
+
+    @Test
+    public void testGroupsWhoLoseOnlyChildToPromotionSuppressSummary() {
+        // GIVEN a group with two children
+        addGroupChild(0, PACKAGE_2, GROUP_1);
+        addGroupSummary(1, PACKAGE_2, GROUP_1);
+
+        // GIVEN a promoter that will promote one of children to top level
+        mListBuilder.addPromoter(new IdPromoter(0));
+
+        // WHEN we build the list
+        dispatchBuild();
+
+        // THEN both children end up at top level (because group is now too small)
+        verifyBuiltList(
+                notif(0)
+        );
+
+        // THEN the summary is discarded
+        assertNull(mEntrySet.get(1).getParent());
+    }
+
+    @Test
     public void testPreviousParentsAreSetProperly() {
         // GIVEN a notification that is initially added to the list
         PackageFilter filter = new PackageFilter(PACKAGE_2);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index c67a233..144eefb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -15,14 +15,20 @@
  */
 package com.android.systemui.statusbar.notification.collection.coordinator
 
+import android.app.Notification.GROUP_ALERT_ALL
+import android.app.Notification.GROUP_ALERT_SUMMARY
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.logcatLogBuffer
 import com.android.systemui.statusbar.NotificationRemoteInputManager
+import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
@@ -32,6 +38,7 @@
 import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider
 import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback
+import com.android.systemui.statusbar.phone.NotificationGroupTestHelper
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -64,10 +71,13 @@
     private lateinit var mCollectionListener: NotifCollectionListener
     private lateinit var mNotifPromoter: NotifPromoter
     private lateinit var mNotifLifetimeExtender: NotifLifetimeExtender
+    private lateinit var mBeforeTransformGroupsListener: OnBeforeTransformGroupsListener
+    private lateinit var mBeforeFinalizeFilterListener: OnBeforeFinalizeFilterListener
     private lateinit var mOnHeadsUpChangedListener: OnHeadsUpChangedListener
     private lateinit var mNotifSectioner: NotifSectioner
 
     private val mNotifPipeline: NotifPipeline = mock()
+    private val mLogger = HeadsUpCoordinatorLogger(logcatLogBuffer(), verbose = true)
     private val mHeadsUpManager: HeadsUpManager = mock()
     private val mHeadsUpViewBinder: HeadsUpViewBinder = mock()
     private val mNotificationInterruptStateProvider: NotificationInterruptStateProvider = mock()
@@ -76,12 +86,24 @@
     private val mHeaderController: NodeController = mock()
 
     private lateinit var mEntry: NotificationEntry
-    private val mExecutor = FakeExecutor(FakeSystemClock())
+    private lateinit var mGroupSummary: NotificationEntry
+    private lateinit var mGroupPriority: NotificationEntry
+    private lateinit var mGroupSibling1: NotificationEntry
+    private lateinit var mGroupSibling2: NotificationEntry
+    private lateinit var mGroupChild1: NotificationEntry
+    private lateinit var mGroupChild2: NotificationEntry
+    private lateinit var mGroupChild3: NotificationEntry
+    private val mSystemClock = FakeSystemClock()
+    private val mExecutor = FakeExecutor(mSystemClock)
     private val mHuns: ArrayList<NotificationEntry> = ArrayList()
+    private lateinit var mHelper: NotificationGroupTestHelper
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        mHelper = NotificationGroupTestHelper(mContext)
         mCoordinator = HeadsUpCoordinator(
+            mLogger,
+            mSystemClock,
             mHeadsUpManager,
             mHeadsUpViewBinder,
             mNotificationInterruptStateProvider,
@@ -100,6 +122,12 @@
         mNotifLifetimeExtender = withArgCaptor {
             verify(mNotifPipeline).addNotificationLifetimeExtender(capture())
         }
+        mBeforeTransformGroupsListener = withArgCaptor {
+            verify(mNotifPipeline).addOnBeforeTransformGroupsListener(capture())
+        }
+        mBeforeFinalizeFilterListener = withArgCaptor {
+            verify(mNotifPipeline).addOnBeforeFinalizeFilterListener(capture())
+        }
         mOnHeadsUpChangedListener = withArgCaptor {
             verify(mHeadsUpManager).addListener(capture())
         }
@@ -116,6 +144,16 @@
         mNotifSectioner = mCoordinator.sectioner
         mNotifLifetimeExtender.setCallback(mEndLifetimeExtension)
         mEntry = NotificationEntryBuilder().build()
+        // Same summary we can use for either set of children
+        mGroupSummary = mHelper.createSummaryNotification(GROUP_ALERT_ALL, 0, "summary", 500)
+        // One set of children with GROUP_ALERT_SUMMARY
+        mGroupPriority = mHelper.createChildNotification(GROUP_ALERT_SUMMARY, 0, "priority", 400)
+        mGroupSibling1 = mHelper.createChildNotification(GROUP_ALERT_SUMMARY, 1, "sibling", 300)
+        mGroupSibling2 = mHelper.createChildNotification(GROUP_ALERT_SUMMARY, 2, "sibling", 200)
+        // Another set of children with GROUP_ALERT_ALL
+        mGroupChild1 = mHelper.createChildNotification(GROUP_ALERT_ALL, 1, "child", 350)
+        mGroupChild2 = mHelper.createChildNotification(GROUP_ALERT_ALL, 2, "child", 250)
+        mGroupChild3 = mHelper.createChildNotification(GROUP_ALERT_ALL, 3, "child", 150)
     }
 
     @Test
@@ -156,6 +194,34 @@
     }
 
     @Test
+    fun testPromotesAddedHUN() {
+        // GIVEN the current entry should heads up
+        whenever(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(true)
+
+        // WHEN the notification is added but not yet binding
+        mCollectionListener.onEntryAdded(mEntry)
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mEntry), any())
+
+        // THEN only promote mEntry
+        assertTrue(mNotifPromoter.shouldPromoteToTopLevel(mEntry))
+    }
+
+    @Test
+    fun testPromotesBindingHUN() {
+        // GIVEN the current entry should heads up
+        whenever(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(true)
+
+        // WHEN the notification started binding on the previous run
+        mCollectionListener.onEntryAdded(mEntry)
+        mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
+        mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+        verify(mHeadsUpViewBinder).bindHeadsUpView(eq(mEntry), any())
+
+        // THEN only promote mEntry
+        assertTrue(mNotifPromoter.shouldPromoteToTopLevel(mEntry))
+    }
+
+    @Test
     fun testPromotesCurrentHUN() {
         // GIVEN the current HUN is set to mEntry
         addHUN(mEntry)
@@ -198,6 +264,9 @@
         whenever(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(true)
 
         mCollectionListener.onEntryAdded(mEntry)
+        mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
+        mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+        verify(mHeadsUpManager, never()).showNotification(mEntry)
         withArgCaptor<BindCallback> {
             verify(mHeadsUpViewBinder).bindHeadsUpView(eq(mEntry), capture())
         }.onBindFinished(mEntry)
@@ -211,6 +280,8 @@
         // WHEN a notification shouldn't HUN and its inflation is finished
         whenever(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(false)
         mCollectionListener.onEntryAdded(mEntry)
+        mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
+        mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
 
         // THEN we never bind the heads up view or tell HeadsUpManager to show the notification
         verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mEntry), any())
@@ -235,4 +306,296 @@
         whenever(mHeadsUpManager.topEntry).thenReturn(entry)
         mOnHeadsUpChangedListener.onHeadsUpStateChanged(entry, true)
     }
-}
\ No newline at end of file
+
+    @Test
+    fun testTransferIsolatedChildAlert_withGroupAlertSummary() {
+        setShouldHeadsUp(mGroupSummary)
+        whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mGroupSummary, mGroupSibling1))
+
+        mCollectionListener.onEntryAdded(mGroupSummary)
+        mCollectionListener.onEntryAdded(mGroupSibling1)
+        mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mGroupSibling1))
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+        mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mGroupSibling1))
+
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any())
+        finishBind(mGroupSibling1)
+
+        verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
+        verify(mHeadsUpManager).showNotification(mGroupSibling1)
+    }
+
+    @Test
+    fun testTransferIsolatedChildAlert_withGroupAlertAll() {
+        setShouldHeadsUp(mGroupSummary)
+        whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mGroupSummary, mGroupChild1))
+
+        mCollectionListener.onEntryAdded(mGroupSummary)
+        mCollectionListener.onEntryAdded(mGroupChild1)
+        mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mGroupChild1))
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+        mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mGroupChild1))
+
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any())
+        finishBind(mGroupChild1)
+
+        verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
+        verify(mHeadsUpManager).showNotification(mGroupChild1)
+    }
+
+    @Test
+    fun testTransferTwoIsolatedChildAlert_withGroupAlertSummary() {
+        // WHEN a notification should HUN and its inflation is finished
+        setShouldHeadsUp(mGroupSummary)
+        whenever(mNotifPipeline.allNotifs)
+            .thenReturn(listOf(mGroupSummary, mGroupSibling1, mGroupSibling2, mGroupPriority))
+
+        mCollectionListener.onEntryAdded(mGroupSummary)
+        mCollectionListener.onEntryAdded(mGroupSibling1)
+        mCollectionListener.onEntryAdded(mGroupSibling2)
+        val entryList = listOf(mGroupSibling1, mGroupSibling2)
+        mBeforeTransformGroupsListener.onBeforeTransformGroups(entryList)
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+        mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(entryList)
+
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any())
+        finishBind(mGroupSibling1)
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling2), any())
+
+        // THEN we tell the HeadsUpManager to show the notification
+        verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
+        verify(mHeadsUpManager).showNotification(mGroupSibling1)
+        verify(mHeadsUpManager, never()).showNotification(mGroupSibling2)
+    }
+
+    @Test
+    fun testTransferTwoIsolatedChildAlert_withGroupAlertAll() {
+        // WHEN a notification should HUN and its inflation is finished
+        setShouldHeadsUp(mGroupSummary)
+        whenever(mNotifPipeline.allNotifs)
+            .thenReturn(listOf(mGroupSummary, mGroupChild1, mGroupChild2, mGroupPriority))
+
+        mCollectionListener.onEntryAdded(mGroupSummary)
+        mCollectionListener.onEntryAdded(mGroupChild1)
+        mCollectionListener.onEntryAdded(mGroupChild2)
+        val entryList = listOf(mGroupChild1, mGroupChild2)
+        mBeforeTransformGroupsListener.onBeforeTransformGroups(entryList)
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+        mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(entryList)
+
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any())
+        finishBind(mGroupChild1)
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupChild2), any())
+
+        // THEN we tell the HeadsUpManager to show the notification
+        verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
+        verify(mHeadsUpManager).showNotification(mGroupChild1)
+        verify(mHeadsUpManager, never()).showNotification(mGroupChild2)
+    }
+
+    @Test
+    fun testTransferToPriorityOnAddWithTwoSiblings() {
+        // WHEN a notification should HUN and its inflation is finished
+        setShouldHeadsUp(mGroupSummary)
+        whenever(mNotifPipeline.allNotifs)
+            .thenReturn(listOf(mGroupSummary, mGroupSibling1, mGroupSibling2, mGroupPriority))
+
+        mCollectionListener.onEntryAdded(mGroupSummary)
+        mCollectionListener.onEntryAdded(mGroupPriority)
+        mCollectionListener.onEntryAdded(mGroupSibling1)
+        mCollectionListener.onEntryAdded(mGroupSibling2)
+
+        val beforeTransformGroup = GroupEntryBuilder()
+            .setSummary(mGroupSummary)
+            .setChildren(listOf(mGroupSibling1, mGroupPriority, mGroupSibling2))
+            .build()
+        mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup))
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+
+        val afterTransformGroup = GroupEntryBuilder()
+            .setSummary(mGroupSummary)
+            .setChildren(listOf(mGroupSibling1, mGroupSibling2))
+            .build()
+        mBeforeFinalizeFilterListener
+            .onBeforeFinalizeFilter(listOf(mGroupPriority, afterTransformGroup))
+
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any())
+        finishBind(mGroupPriority)
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling1), any())
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling2), any())
+
+        // THEN we tell the HeadsUpManager to show the notification
+        verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
+        verify(mHeadsUpManager).showNotification(mGroupPriority)
+        verify(mHeadsUpManager, never()).showNotification(mGroupSibling1)
+        verify(mHeadsUpManager, never()).showNotification(mGroupSibling2)
+    }
+
+    @Test
+    fun testTransferToPriorityOnUpdateWithTwoSiblings() {
+        setShouldHeadsUp(mGroupSummary)
+        whenever(mNotifPipeline.allNotifs)
+            .thenReturn(listOf(mGroupSummary, mGroupSibling1, mGroupSibling2, mGroupPriority))
+
+        mCollectionListener.onEntryUpdated(mGroupSummary)
+        mCollectionListener.onEntryUpdated(mGroupPriority)
+        mCollectionListener.onEntryUpdated(mGroupSibling1)
+        mCollectionListener.onEntryUpdated(mGroupSibling2)
+
+        val beforeTransformGroup = GroupEntryBuilder()
+            .setSummary(mGroupSummary)
+            .setChildren(listOf(mGroupSibling1, mGroupPriority, mGroupSibling2))
+            .build()
+        mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup))
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+
+        val afterTransformGroup = GroupEntryBuilder()
+            .setSummary(mGroupSummary)
+            .setChildren(listOf(mGroupSibling1, mGroupSibling2))
+            .build()
+        mBeforeFinalizeFilterListener
+            .onBeforeFinalizeFilter(listOf(mGroupPriority, afterTransformGroup))
+
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any())
+        finishBind(mGroupPriority)
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling1), any())
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling2), any())
+
+        verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
+        verify(mHeadsUpManager).showNotification(mGroupPriority)
+        verify(mHeadsUpManager, never()).showNotification(mGroupSibling1)
+        verify(mHeadsUpManager, never()).showNotification(mGroupSibling2)
+    }
+
+    @Test
+    fun testTransferToPriorityOnUpdateWithTwoNonUpdatedSiblings() {
+        setShouldHeadsUp(mGroupSummary)
+        whenever(mNotifPipeline.allNotifs)
+            .thenReturn(listOf(mGroupSummary, mGroupSibling1, mGroupSibling2, mGroupPriority))
+
+        mCollectionListener.onEntryUpdated(mGroupSummary)
+        mCollectionListener.onEntryUpdated(mGroupPriority)
+
+        val beforeTransformGroup = GroupEntryBuilder()
+            .setSummary(mGroupSummary)
+            .setChildren(listOf(mGroupSibling1, mGroupPriority, mGroupSibling2))
+            .build()
+        mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup))
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+
+        val afterTransformGroup = GroupEntryBuilder()
+            .setSummary(mGroupSummary)
+            .setChildren(listOf(mGroupSibling1, mGroupSibling2))
+            .build()
+        mBeforeFinalizeFilterListener
+            .onBeforeFinalizeFilter(listOf(mGroupPriority, afterTransformGroup))
+
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any())
+        finishBind(mGroupPriority)
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling1), any())
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling2), any())
+
+        verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
+        verify(mHeadsUpManager).showNotification(mGroupPriority)
+        verify(mHeadsUpManager, never()).showNotification(mGroupSibling1)
+        verify(mHeadsUpManager, never()).showNotification(mGroupSibling2)
+    }
+
+    @Test
+    fun testNoTransferToPriorityOnUpdateOfTwoSiblings() {
+        setShouldHeadsUp(mGroupSummary)
+        whenever(mNotifPipeline.allNotifs)
+            .thenReturn(listOf(mGroupSummary, mGroupSibling1, mGroupSibling2, mGroupPriority))
+
+        mCollectionListener.onEntryUpdated(mGroupSummary)
+        mCollectionListener.onEntryUpdated(mGroupSibling1)
+        mCollectionListener.onEntryUpdated(mGroupSibling2)
+
+        val beforeTransformGroup = GroupEntryBuilder()
+            .setSummary(mGroupSummary)
+            .setChildren(listOf(mGroupSibling1, mGroupPriority, mGroupSibling2))
+            .build()
+        mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup))
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+
+        val afterTransformGroup = GroupEntryBuilder()
+            .setSummary(mGroupSummary)
+            .setChildren(listOf(mGroupSibling1, mGroupSibling2))
+            .build()
+        mBeforeFinalizeFilterListener
+            .onBeforeFinalizeFilter(listOf(mGroupPriority, afterTransformGroup))
+
+        finishBind(mGroupSummary)
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupPriority), any())
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling1), any())
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling2), any())
+
+        verify(mHeadsUpManager).showNotification(mGroupSummary)
+        verify(mHeadsUpManager, never()).showNotification(mGroupPriority)
+        verify(mHeadsUpManager, never()).showNotification(mGroupSibling1)
+        verify(mHeadsUpManager, never()).showNotification(mGroupSibling2)
+    }
+
+    @Test
+    fun testNoTransferTwoChildAlert_withGroupAlertSummary() {
+        setShouldHeadsUp(mGroupSummary)
+        whenever(mNotifPipeline.allNotifs)
+            .thenReturn(listOf(mGroupSummary, mGroupSibling1, mGroupSibling2))
+
+        mCollectionListener.onEntryAdded(mGroupSummary)
+        mCollectionListener.onEntryAdded(mGroupSibling1)
+        mCollectionListener.onEntryAdded(mGroupSibling2)
+        val groupEntry = GroupEntryBuilder()
+            .setSummary(mGroupSummary)
+            .setChildren(listOf(mGroupSibling1, mGroupSibling2))
+            .build()
+        mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupEntry))
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+        mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
+
+        finishBind(mGroupSummary)
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling1), any())
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling2), any())
+
+        verify(mHeadsUpManager).showNotification(mGroupSummary)
+        verify(mHeadsUpManager, never()).showNotification(mGroupSibling1)
+        verify(mHeadsUpManager, never()).showNotification(mGroupSibling2)
+    }
+
+    @Test
+    fun testNoTransferTwoChildAlert_withGroupAlertAll() {
+        setShouldHeadsUp(mGroupSummary)
+        whenever(mNotifPipeline.allNotifs)
+            .thenReturn(listOf(mGroupSummary, mGroupChild1, mGroupChild2))
+
+        mCollectionListener.onEntryAdded(mGroupSummary)
+        mCollectionListener.onEntryAdded(mGroupChild1)
+        mCollectionListener.onEntryAdded(mGroupChild2)
+        val groupEntry = GroupEntryBuilder()
+            .setSummary(mGroupSummary)
+            .setChildren(listOf(mGroupChild1, mGroupChild2))
+            .build()
+        mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupEntry))
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+        mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
+
+        finishBind(mGroupSummary)
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupChild1), any())
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupChild2), any())
+
+        verify(mHeadsUpManager).showNotification(mGroupSummary)
+        verify(mHeadsUpManager, never()).showNotification(mGroupChild1)
+        verify(mHeadsUpManager, never()).showNotification(mGroupChild2)
+    }
+
+    private fun setShouldHeadsUp(entry: NotificationEntry, should: Boolean = true) {
+        whenever(mNotificationInterruptStateProvider.shouldHeadsUp(entry)).thenReturn(should)
+    }
+
+    private fun finishBind(entry: NotificationEntry) {
+        verify(mHeadsUpManager, never()).showNotification(entry)
+        withArgCaptor<BindCallback> {
+            verify(mHeadsUpViewBinder).bindHeadsUpView(eq(entry), capture())
+        }.onBindFinished(entry)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/MediaContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/MediaContainerViewTest.kt
new file mode 100644
index 0000000..0909ff2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/MediaContainerViewTest.kt
@@ -0,0 +1,38 @@
+package com.android.systemui.statusbar.notification.stack
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.LayoutInflater
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.R
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for {@link MediaContainView}.
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class MediaContainerViewTest : SysuiTestCase() {
+
+    lateinit var mediaContainerView : MediaContainerView
+
+    @Before
+    fun setUp() {
+        mediaContainerView = LayoutInflater.from(context).inflate(
+                R.layout.keyguard_media_container, null, false) as MediaContainerView
+    }
+
+    @Test
+    fun testUpdateClipping_updatesClipHeight() {
+        assertTrue(mediaContainerView.clipHeight == 0)
+
+        mediaContainerView.actualHeight = 10
+        mediaContainerView.updateClipping()
+        assertTrue(mediaContainerView.clipHeight == 10)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
new file mode 100644
index 0000000..d280f54
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -0,0 +1,153 @@
+package com.android.systemui.statusbar.notification.stack
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.NotificationShelf
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.*
+import org.mockito.Mockito.`when` as whenever
+
+/**
+ * Tests for {@link NotificationShelf}.
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotificationShelfTest : SysuiTestCase() {
+
+    private val shelf = NotificationShelf(context, /* attrs */ null)
+    private val shelfState = shelf.viewState as NotificationShelf.ShelfState
+    private val ambientState = mock(AmbientState::class.java)
+
+    @Before
+    fun setUp() {
+        shelf.bind(ambientState, /* hostLayoutController */ null)
+        shelf.layout(/* left */ 0, /* top */ 0, /* right */ 30, /* bottom */5)
+    }
+
+    @Test
+    fun testShadeWidth_BasedOnFractionToShade() {
+        setFractionToShade(0f)
+        setOnLockscreen(true)
+
+        shelf.updateStateWidth(shelfState, /* fraction */ 0f, /* shortestWidth */ 10)
+        assertTrue(shelfState.actualWidth == 10)
+
+        shelf.updateStateWidth(shelfState, /* fraction */ 0.5f, /* shortestWidth */ 10)
+        assertTrue(shelfState.actualWidth == 20)
+
+        shelf.updateStateWidth(shelfState, /* fraction */ 1f, /* shortestWidth */ 10)
+        assertTrue(shelfState.actualWidth == 30)
+    }
+
+    @Test
+    fun testShelfIsLong_WhenNotOnLockscreen() {
+        setFractionToShade(0f)
+        setOnLockscreen(false)
+
+        shelf.updateStateWidth(shelfState, /* fraction */ 0f, /* shortestWidth */ 10)
+        assertTrue(shelfState.actualWidth == 30)
+    }
+
+    @Test
+    fun testX_inViewForClick() {
+        val isXInView = shelf.isXInView(
+                /* localX */ 5f,
+                /* slop */ 5f,
+                /* left */ 0f,
+                /* right */ 10f)
+        assertTrue(isXInView)
+    }
+
+    @Test
+    fun testXSlop_inViewForClick() {
+        val isLeftXSlopInView = shelf.isXInView(
+                /* localX */ -3f,
+                /* slop */ 5f,
+                /* left */ 0f,
+                /* right */ 10f)
+        assertTrue(isLeftXSlopInView)
+
+        val isRightXSlopInView = shelf.isXInView(
+                /* localX */ 13f,
+                /* slop */ 5f,
+                /* left */ 0f,
+                /* right */ 10f)
+        assertTrue(isRightXSlopInView)
+    }
+
+    @Test
+    fun testX_notInViewForClick() {
+        val isXLeftOfShelfInView = shelf.isXInView(
+                /* localX */ -10f,
+                /* slop */ 5f,
+                /* left */ 0f,
+                /* right */ 10f)
+        assertFalse(isXLeftOfShelfInView)
+
+        val isXRightOfShelfInView = shelf.isXInView(
+                /* localX */ 20f,
+                /* slop */ 5f,
+                /* left */ 0f,
+                /* right */ 10f)
+        assertFalse(isXRightOfShelfInView)
+    }
+
+    @Test
+    fun testY_inViewForClick() {
+        val isYInView = shelf.isYInView(
+                /* localY */ 5f,
+                /* slop */ 5f,
+                /* top */ 0f,
+                /* bottom */ 10f)
+        assertTrue(isYInView)
+    }
+
+    @Test
+    fun testYSlop_inViewForClick() {
+        val isTopYSlopInView = shelf.isYInView(
+                /* localY */ -3f,
+                /* slop */ 5f,
+                /* top */ 0f,
+                /* bottom */ 10f)
+        assertTrue(isTopYSlopInView)
+
+        val isBottomYSlopInView = shelf.isYInView(
+                /* localY */ 13f,
+                /* slop */ 5f,
+                /* top */ 0f,
+                /* bottom */ 10f)
+        assertTrue(isBottomYSlopInView)
+    }
+
+    @Test
+    fun testY_notInViewForClick() {
+        val isYAboveShelfInView = shelf.isYInView(
+                /* localY */ -10f,
+                /* slop */ 5f,
+                /* top */ 0f,
+                /* bottom */ 5f)
+        assertFalse(isYAboveShelfInView)
+
+        val isYBelowShelfInView = shelf.isYInView(
+                /* localY */ 15f,
+                /* slop */ 5f,
+                /* top */ 0f,
+                /* bottom */ 5f)
+        assertFalse(isYBelowShelfInView)
+    }
+
+    private fun setFractionToShade(fraction: Float) {
+        shelf.setFractionToShade(fraction)
+    }
+
+    private fun setOnLockscreen(isOnLockscreen: Boolean) {
+        whenever(ambientState.isOnKeyguard).thenReturn(isOnLockscreen)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 46ba097..bdcbbbc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -27,6 +27,7 @@
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
 
 import static org.junit.Assert.assertFalse;
 import static org.mockito.ArgumentMatchers.any;
@@ -44,6 +45,7 @@
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.util.MathUtils;
 
 import androidx.test.annotation.UiThreadTest;
 import androidx.test.filters.SmallTest;
@@ -165,6 +167,47 @@
     }
 
     @Test
+    public void testUpdateStackEndHeight_forEndOfStackHeightAnimation() {
+        final float nsslHeight = 10f;
+        final float bottomMargin = 1f;
+        final float topPadding = 1f;
+
+        mStackScroller.updateStackEndHeight(nsslHeight, bottomMargin, topPadding);
+        final float stackEndHeight = nsslHeight - bottomMargin - topPadding;
+        assertTrue(mAmbientState.getStackEndHeight() == stackEndHeight);
+    }
+
+    @Test
+    public void testUpdateStackHeight_withDozeAmount_whenDozeChanging() {
+        final float dozeAmount = 0.5f;
+        mAmbientState.setDozeAmount(dozeAmount);
+
+        final float endHeight = 8f;
+        final float expansionFraction = 1f;
+        float expected = MathUtils.lerp(
+                endHeight * StackScrollAlgorithm.START_FRACTION,
+                endHeight, dozeAmount);
+
+        mStackScroller.updateStackHeight(endHeight, expansionFraction);
+        assertTrue(mAmbientState.getStackHeight() == expected);
+    }
+
+    @Test
+    public void testUpdateStackHeight_withExpansionAmount_whenDozeNotChanging() {
+        final float dozeAmount = 1f;
+        mAmbientState.setDozeAmount(dozeAmount);
+
+        final float endHeight = 8f;
+        final float expansionFraction = 0.5f;
+        final float expected = MathUtils.lerp(
+                endHeight * StackScrollAlgorithm.START_FRACTION,
+                endHeight, expansionFraction);
+
+        mStackScroller.updateStackHeight(endHeight, expansionFraction);
+        assertTrue(mAmbientState.getStackHeight() == expected);
+    }
+
+    @Test
     public void testNotDimmedOnKeyguard() {
         when(mBarState.getState()).thenReturn(StatusBarState.SHADE);
         mStackScroller.setDimmed(true /* dimmed */, false /* animate */);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 5ca1f21..8c7d22d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -46,6 +46,7 @@
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -107,6 +108,8 @@
     private StatusBarStateController mStatusBarStateController;
     @Mock
     private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
+    @Mock
+    private SessionTracker mSessionTracker;
     private BiometricUnlockController mBiometricUnlockController;
 
     @Before
@@ -129,7 +132,8 @@
                 mUpdateMonitor, res.getResources(), mKeyguardBypassController, mDozeParameters,
                 mMetricsLogger, mDumpManager, mPowerManager,
                 mNotificationMediaManager, mWakefulnessLifecycle, mScreenLifecycle,
-                mAuthController, mStatusBarStateController, mKeyguardUnlockAnimationController);
+                mAuthController, mStatusBarStateController, mKeyguardUnlockAnimationController,
+                mSessionTracker);
         mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
         mBiometricUnlockController.setBiometricModeListener(mBiometricModeListener);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java
index ac32b9d..47f15a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java
@@ -59,6 +59,13 @@
         return createEntry(id, tag, true, groupAlertBehavior);
     }
 
+    public NotificationEntry createSummaryNotification(
+            int groupAlertBehavior, int id, String tag, long when) {
+        NotificationEntry entry = createSummaryNotification(groupAlertBehavior, id, tag);
+        entry.getSbn().getNotification().when = when;
+        return entry;
+    }
+
     public NotificationEntry createChildNotification() {
         return createChildNotification(Notification.GROUP_ALERT_ALL);
     }
@@ -71,6 +78,13 @@
         return createEntry(id, tag, false, groupAlertBehavior);
     }
 
+    public NotificationEntry createChildNotification(
+            int groupAlertBehavior, int id, String tag, long when) {
+        NotificationEntry entry = createChildNotification(groupAlertBehavior, id, tag);
+        entry.getSbn().getNotification().when = when;
+        return entry;
+    }
+
     public NotificationEntry createEntry(int id, String tag, boolean isSummary,
             int groupAlertBehavior) {
         Notification notif = new Notification.Builder(mContext, TEST_CHANNEL_ID)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 5d80bca..bb79941 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -43,6 +43,7 @@
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dock.DockManager;
+import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import com.android.systemui.statusbar.NotificationMediaManager;
@@ -97,6 +98,8 @@
     private KeyguardMessageArea mKeyguardMessageArea;
     @Mock
     private ShadeController mShadeController;
+    @Mock
+    private DreamOverlayStateController mDreamOverlayStateController;
 
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
 
@@ -116,6 +119,7 @@
                 mStatusBarStateController,
                 mock(ConfigurationController.class),
                 mKeyguardUpdateMonitor,
+                mDreamOverlayStateController,
                 mock(NavigationModeController.class),
                 mock(DockManager.class),
                 mock(NotificationShadeWindowController.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index b7c00fe..1564dfe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -87,6 +87,7 @@
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.fragments.FragmentService;
@@ -285,6 +286,7 @@
     @Mock private NotifLiveDataStore mNotifLiveDataStore;
     @Mock private InteractionJankMonitor mJankMonitor;
     @Mock private DeviceStateManager mDeviceStateManager;
+    @Mock private DreamOverlayStateController mDreamOverlayStateController;
     private ShadeController mShadeController;
     private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
     private FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
@@ -474,7 +476,8 @@
                 mActivityLaunchAnimator,
                 mNotifPipelineFlags,
                 mJankMonitor,
-                mDeviceStateManager);
+                mDeviceStateManager,
+                mDreamOverlayStateController);
         when(mKeyguardViewMediator.registerStatusBar(
                 any(StatusBar.class),
                 any(NotificationPanelViewController.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index c9462d6..b380553 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -33,7 +33,6 @@
 import android.media.session.MediaSession;
 import android.os.Handler;
 import android.os.Process;
-import android.os.Vibrator;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.accessibility.AccessibilityManager;
@@ -43,6 +42,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.util.RingerModeLiveData;
 import com.android.systemui.util.RingerModeTracker;
 import com.android.systemui.util.concurrency.FakeExecutor;
@@ -57,8 +57,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.Optional;
-
 @RunWith(AndroidTestingRunner.class)
 @SmallTest
 @TestableLooper.RunWithLooper
@@ -81,7 +79,7 @@
     @Mock
     private NotificationManager mNotificationManager;
     @Mock
-    private Vibrator mVibrator;
+    private VibratorHelper mVibrator;
     @Mock
     private IAudioService mIAudioService;
     @Mock
@@ -110,7 +108,7 @@
         mThreadFactory.setLooper(TestableLooper.get(this).getLooper());
         mVolumeController = new TestableVolumeDialogControllerImpl(mContext,
                 mBroadcastDispatcher, mRingerModeTracker, mThreadFactory, mAudioManager,
-                mNotificationManager, Optional.of(mVibrator), mIAudioService, mAccessibilityManager,
+                mNotificationManager, mVibrator, mIAudioService, mAccessibilityManager,
                 mPackageManager, mWakefullnessLifcycle, mCallback);
         mVolumeController.setEnableDialogs(true, true);
     }
@@ -181,7 +179,7 @@
                 ThreadFactory theadFactory,
                 AudioManager audioManager,
                 NotificationManager notificationManager,
-                Optional<Vibrator> optionalVibrator,
+                VibratorHelper optionalVibrator,
                 IAudioService iAudioService,
                 AccessibilityManager accessibilityManager,
                 PackageManager packageManager,
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 2168fb1..a65d5b3 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -3346,8 +3346,10 @@
             // Isolate the changes relating to RROs. The app info must be copied to prevent
             // affecting other parts of system server that may have cached this app info.
             oldAppInfo = new ApplicationInfo(oldAppInfo);
-            oldAppInfo.overlayPaths = newAppInfo.overlayPaths.clone();
-            oldAppInfo.resourceDirs = newAppInfo.resourceDirs.clone();
+            oldAppInfo.overlayPaths = newAppInfo.overlayPaths == null
+                    ? null : newAppInfo.overlayPaths.clone();
+            oldAppInfo.resourceDirs = newAppInfo.resourceDirs == null
+                    ? null : newAppInfo.resourceDirs.clone();
             provider.info.providerInfo.applicationInfo = oldAppInfo;
 
             for (int j = 0, M = provider.widgets.size(); j < M; j++) {
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index aa42e8d..1cff374 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -284,7 +284,9 @@
         }
         final Session session = mSessions.get(sessionId);
         if (session != null && uid == session.uid) {
-            session.setAuthenticationResultLocked(data, authenticationId);
+            synchronized (session.mLock) {
+                session.setAuthenticationResultLocked(data, authenticationId);
+            }
         }
     }
 
@@ -374,7 +376,9 @@
                 + " hc=" + hasCallback + " f=" + flags + " aa=" + forAugmentedAutofillOnly;
         mMaster.logRequestLocked(historyItem);
 
-        newSession.updateLocked(autofillId, virtualBounds, value, ACTION_START_SESSION, flags);
+        synchronized (newSession.mLock) {
+            newSession.updateLocked(autofillId, virtualBounds, value, ACTION_START_SESSION, flags);
+        }
 
         if (forAugmentedAutofillOnly) {
             // Must embed the flag in the response, at the high-end side of the long.
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index a4bf52a..76ee728 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -17,6 +17,8 @@
 package com.android.server.autofill;
 
 import static android.service.autofill.AutofillFieldClassificationService.EXTRA_SCORES;
+import static android.service.autofill.AutofillService.EXTRA_FILL_RESPONSE;
+import static android.service.autofill.FillRequest.FLAG_ACTIVITY_START;
 import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
 import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE;
 import static android.service.autofill.FillRequest.FLAG_VIEW_NOT_FOCUSED;
@@ -46,13 +48,16 @@
 import android.app.Activity;
 import android.app.ActivityTaskManager;
 import android.app.IAssistDataReceiver;
+import android.app.PendingIntent;
 import android.app.assist.AssistStructure;
 import android.app.assist.AssistStructure.AutofillOverlay;
 import android.app.assist.AssistStructure.ViewNode;
+import android.content.BroadcastReceiver;
 import android.content.ClipData;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.IntentSender;
 import android.content.pm.ApplicationInfo;
 import android.graphics.Bitmap;
@@ -147,12 +152,16 @@
         AutoFillUI.AutoFillUiCallback, ValueFinder {
     private static final String TAG = "AutofillSession";
 
+    private static final String ACTION_DELAYED_FILL =
+            "android.service.autofill.action.DELAYED_FILL";
     private static final String EXTRA_REQUEST_ID = "android.service.autofill.extra.REQUEST_ID";
 
+    final Object mLock;
+
     private final AutofillManagerServiceImpl mService;
     private final Handler mHandler;
-    private final Object mLock;
     private final AutoFillUI mUi;
+    @NonNull private final Context mContext;
 
     private final MetricsLogger mMetricsLogger = new MetricsLogger();
 
@@ -267,6 +276,12 @@
      */
     private boolean mHasCallback;
 
+    @GuardedBy("mLock")
+    private boolean mDelayedFillBroadcastReceiverRegistered;
+
+    @GuardedBy("mLock")
+    private PendingIntent mDelayedFillPendingIntent;
+
     /**
      * Extras sent by service on {@code onFillRequest()} calls; the most recent non-null extra is
      * saved and used on subsequent {@code onFillRequest()} and {@code onSaveRequest()} calls.
@@ -354,6 +369,32 @@
 
     private final AccessibilityManager mAccessibilityManager;
 
+    // TODO(b/216576510): Share one BroadcastReceiver between all Sessions instead of creating a
+    // new one per Session.
+    private final BroadcastReceiver mDelayedFillBroadcastReceiver =
+            new BroadcastReceiver() {
+                // ErrorProne says mAssistReceiver#processDelayedFillLocked needs to be guarded by
+                // 'Session.this.mLock', which is the same as mLock.
+                @SuppressWarnings("GuardedBy")
+                @Override
+                public void onReceive(final Context context, final Intent intent) {
+                    if (!intent.getAction().equals(ACTION_DELAYED_FILL)) {
+                        Slog.wtf(TAG, "Unexpected action is received.");
+                        return;
+                    }
+                    if (!intent.hasExtra(EXTRA_REQUEST_ID)) {
+                        Slog.e(TAG, "Delay fill action is missing request id extra.");
+                        return;
+                    }
+                    Slog.v(TAG, "mDelayedFillBroadcastReceiver delayed fill action received");
+                    synchronized (mLock) {
+                        int requestId = intent.getIntExtra(EXTRA_REQUEST_ID, 0);
+                        FillResponse response = intent.getParcelableExtra(EXTRA_FILL_RESPONSE);
+                        mAssistReceiver.processDelayedFillLocked(requestId, response);
+                    }
+                }
+            };
+
     void onSwitchInputMethodLocked() {
         // One caveat is that for the case where the focus is on a field for which regular autofill
         // returns null, and augmented autofill is triggered,  and then the user switches the input
@@ -406,28 +447,25 @@
      */
     private final class SessionFlags {
         /** Whether autofill is disabled by the service */
-        @GuardedBy("mLock")
         private boolean mAutofillDisabled;
 
         /** Whether the autofill service supports inline suggestions */
-        @GuardedBy("mLock")
         private boolean mInlineSupportedByService;
 
         /** True if session is for augmented only */
-        @GuardedBy("mLock")
         private boolean mAugmentedAutofillOnly;
 
         /** Whether the session is currently showing the SaveUi. */
-        @GuardedBy("mLock")
         private boolean mShowingSaveUi;
 
         /** Whether the current {@link FillResponse} is expired. */
-        @GuardedBy("mLock")
         private boolean mExpiredResponse;
 
         /** Whether the client is using {@link android.view.autofill.AutofillRequestCallback}. */
-        @GuardedBy("mLock")
         private boolean mClientSuggestionsEnabled;
+
+        /** Whether the fill dialog UI is disabled. */
+        private boolean mFillDialogDisabled;
     }
 
     /**
@@ -441,6 +479,8 @@
         private InlineSuggestionsRequest mPendingInlineSuggestionsRequest;
         @GuardedBy("mLock")
         private FillRequest mPendingFillRequest;
+        @GuardedBy("mLock")
+        private FillRequest mLastFillRequest;
 
         @Nullable Consumer<InlineSuggestionsRequest> newAutofillRequestLocked(ViewState viewState,
                 boolean isInlineRequest) {
@@ -467,6 +507,7 @@
             mPendingInlineSuggestionsRequest = inlineRequest;
         }
 
+        @GuardedBy("mLock")
         void maybeRequestFillFromServiceLocked() {
             if (mPendingFillRequest == null) {
                 return;
@@ -484,9 +525,12 @@
                     mPendingFillRequest = new FillRequest(mPendingFillRequest.getId(),
                             mPendingFillRequest.getFillContexts(),
                             mPendingFillRequest.getClientState(),
-                            mPendingFillRequest.getFlags(), mPendingInlineSuggestionsRequest);
+                            mPendingFillRequest.getFlags(),
+                            mPendingInlineSuggestionsRequest,
+                            mPendingFillRequest.getDelayedFillIntentSender());
                 }
             }
+            mLastFillRequest = mPendingFillRequest;
 
             mRemoteFillService.onFillRequest(mPendingFillRequest);
             mPendingInlineSuggestionsRequest = null;
@@ -588,8 +632,12 @@
 
                 final ArrayList<FillContext> contexts =
                         mergePreviousSessionLocked(/* forSave= */ false);
+                mDelayedFillPendingIntent = createPendingIntent(requestId);
                 request = new FillRequest(requestId, contexts, mClientState, flags,
-                        /*inlineSuggestionsRequest=*/null);
+                        /*inlineSuggestionsRequest=*/ null,
+                        /*delayedFillIntentSender=*/ mDelayedFillPendingIntent == null
+                            ? null
+                            : mDelayedFillPendingIntent.getIntentSender());
 
                 mPendingFillRequest = request;
                 maybeRequestFillFromServiceLocked();
@@ -604,7 +652,70 @@
         public void onHandleAssistScreenshot(Bitmap screenshot) {
             // Do nothing
         }
-    };
+
+        @GuardedBy("mLock")
+        void processDelayedFillLocked(int requestId, FillResponse response) {
+            if (mLastFillRequest != null && requestId == mLastFillRequest.getId()) {
+                Slog.v(TAG, "processDelayedFillLocked: "
+                        + "calling onFillRequestSuccess with new response");
+                onFillRequestSuccess(requestId, response,
+                        mService.getServicePackageName(), mLastFillRequest.getFlags());
+            }
+        }
+    }
+
+    /** Creates {@link PendingIntent} for autofill service to send a delayed fill. */
+    private PendingIntent createPendingIntent(int requestId) {
+        Slog.d(TAG, "createPendingIntent for request " + requestId);
+        PendingIntent pendingIntent;
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            Intent intent = new Intent(ACTION_DELAYED_FILL).setPackage("android")
+                    .putExtra(EXTRA_REQUEST_ID, requestId);
+            pendingIntent = PendingIntent.getBroadcast(
+                    mContext, this.id, intent,
+                    PendingIntent.FLAG_MUTABLE
+                        | PendingIntent.FLAG_ONE_SHOT
+                        | PendingIntent.FLAG_CANCEL_CURRENT);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+        return pendingIntent;
+    }
+
+    @GuardedBy("mLock")
+    private void clearPendingIntentLocked() {
+        Slog.d(TAG, "clearPendingIntentLocked");
+        if (mDelayedFillPendingIntent == null) {
+            return;
+        }
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            mDelayedFillPendingIntent.cancel();
+            mDelayedFillPendingIntent = null;
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void registerDelayedFillBroadcastLocked() {
+        if (!mDelayedFillBroadcastReceiverRegistered) {
+            Slog.v(TAG, "registerDelayedFillBroadcastLocked()");
+            IntentFilter intentFilter = new IntentFilter(ACTION_DELAYED_FILL);
+            mContext.registerReceiver(mDelayedFillBroadcastReceiver, intentFilter);
+            mDelayedFillBroadcastReceiverRegistered = true;
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void unregisterDelayedFillBroadcastLocked() {
+        if (mDelayedFillBroadcastReceiverRegistered) {
+            Slog.v(TAG, "unregisterDelayedFillBroadcastLocked()");
+            mContext.unregisterReceiver(mDelayedFillBroadcastReceiver);
+            mDelayedFillBroadcastReceiverRegistered = false;
+        }
+    }
 
     /**
      * Returns the ids of all entries in {@link #mViewStates} in the same order.
@@ -860,7 +971,7 @@
                 mService.getRemoteInlineSuggestionRenderServiceLocked();
         if ((mSessionFlags.mInlineSupportedByService || mSessionFlags.mClientSuggestionsEnabled)
                 && remoteRenderService != null
-                && isViewFocusedLocked(flags)) {
+                && (isViewFocusedLocked(flags) || (isRequestFromActivityStarted(flags)))) {
             final Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer;
             if (mSessionFlags.mClientSuggestionsEnabled) {
                 final int finalRequestId = requestId;
@@ -906,6 +1017,10 @@
         requestAssistStructureLocked(requestId, flags);
     }
 
+    private boolean isRequestFromActivityStarted(int flags) {
+        return (flags & FLAG_ACTIVITY_START) != 0;
+    }
+
     @GuardedBy("mLock")
     private void requestAssistStructureLocked(int requestId, int flags) {
         try {
@@ -954,6 +1069,7 @@
         mHasCallback = hasCallback;
         mUiLatencyHistory = uiLatencyHistory;
         mWtfHistory = wtfHistory;
+        mContext = context;
         mComponentName = componentName;
         mCompatMode = compatMode;
         mSessionState = STATE_ACTIVE;
@@ -1086,6 +1202,12 @@
                 processNullResponseLocked(requestId, requestFlags);
                 return;
             }
+
+            final int flags = response.getFlags();
+            if ((flags & FillResponse.FLAG_DELAY_FILL) != 0) {
+                Slog.v(TAG, "Service requested to wait for delayed fill response.");
+                registerDelayedFillBroadcastLocked();
+            }
         }
 
         mService.setLastResponse(id, response);
@@ -1196,6 +1318,7 @@
             @Nullable CharSequence message) {
         boolean showMessage = !TextUtils.isEmpty(message);
         synchronized (mLock) {
+            unregisterDelayedFillBroadcastLocked();
             if (mDestroyed) {
                 Slog.w(TAG, "Call to Session#onFillRequestFailureOrTimeout(req=" + requestId
                         + ") rejected - session: " + id + " destroyed");
@@ -1501,6 +1624,23 @@
                 this, intentSender, intent));
     }
 
+    // AutoFillUiCallback
+    @Override
+    public void requestShowSoftInput(AutofillId id) {
+        IAutoFillManagerClient client = getClient();
+        if (client != null) {
+            try {
+                client.requestShowSoftInput(id);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Error sending input show up notification", e);
+            }
+        }
+        synchronized (mLock) {
+            // stop to show fill dialog
+            mSessionFlags.mFillDialogDisabled = true;
+        }
+    }
+
     private void notifyFillUiHidden(@NonNull AutofillId autofillId) {
         synchronized (mLock) {
             try {
@@ -2869,6 +3009,9 @@
                 // View is triggering autofill.
                 mCurrentViewId = viewState.id;
                 viewState.update(value, virtualBounds, flags);
+                if (!isRequestFromActivityStarted(flags)) {
+                    mSessionFlags.mFillDialogDisabled = true;
+                }
                 requestNewFillResponseLocked(viewState, ViewState.STATE_STARTED_SESSION, flags);
                 break;
             case ACTION_VALUE_CHANGED:
@@ -2958,6 +3101,7 @@
                 }
 
                 if (isSameViewEntered) {
+                    setFillDialogDisabledAndStartInput();
                     return;
                 }
 
@@ -2968,6 +3112,7 @@
                 if (Objects.equals(mCurrentViewId, viewState.id)) {
                     if (sVerbose) Slog.v(TAG, "Exiting view " + id);
                     mUi.hideFillUi(this);
+                    mUi.hideFillDialog(this);
                     hideAugmentedAutofillLocked(viewState);
                     // We don't send an empty response to IME so that it doesn't cause UI flicker
                     // on the IME side if it arrives before the input view is finished on the IME.
@@ -3148,6 +3293,17 @@
             return;
         }
 
+        if (requestShowFillDialog(response, filledId, filterText)) {
+            synchronized (mLock) {
+                final ViewState currentView = mViewStates.get(mCurrentViewId);
+                currentView.setState(ViewState.STATE_FILL_DIALOG_SHOWN);
+                mService.logDatasetShown(id, mClientState);
+            }
+            return;
+        }
+
+        setFillDialogDisabled();
+
         if (response.supportsInlineSuggestions()) {
             synchronized (mLock) {
                 if (requestShowInlineSuggestionsLocked(response, filterText)) {
@@ -3192,6 +3348,81 @@
         }
     }
 
+    @GuardedBy("mLock")
+    private void updateFillDialogTriggerIdsLocked() {
+        final FillResponse response = getLastResponseLocked(null);
+
+        if (response == null) return;
+
+        final AutofillId[] ids = response.getFillDialogTriggerIds();
+        notifyClientFillDialogTriggerIds(ids == null ? null : Arrays.asList(ids));
+    }
+
+    private void notifyClientFillDialogTriggerIds(List<AutofillId> fieldIds) {
+        try {
+            if (sVerbose) {
+                Slog.v(TAG, "notifyFillDialogTriggerIds(): " + fieldIds);
+            }
+            getClient().notifyFillDialogTriggerIds(fieldIds);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Cannot set trigger ids for fill dialog", e);
+        }
+    }
+
+    private boolean isFillDialogUiEnabled() {
+        // TODO read from Settings or somewhere
+        final boolean isSettingsEnabledFillDialog = true;
+        synchronized (mLock) {
+            return isSettingsEnabledFillDialog && !mSessionFlags.mFillDialogDisabled;
+        }
+    }
+
+    private void setFillDialogDisabled() {
+        synchronized (mLock) {
+            mSessionFlags.mFillDialogDisabled = true;
+        }
+        notifyClientFillDialogTriggerIds(null);
+    }
+
+    private void setFillDialogDisabledAndStartInput() {
+        if (getUiForShowing().isFillDialogShowing()) {
+            setFillDialogDisabled();
+            final AutofillId id;
+            synchronized (mLock) {
+                id = mCurrentViewId;
+            }
+            requestShowSoftInput(id);
+        }
+    }
+
+    private boolean requestShowFillDialog(FillResponse response,
+            AutofillId filledId, String filterText) {
+        if (!isFillDialogUiEnabled()) {
+            // Unsupported fill dialog UI
+            return false;
+        }
+
+        final AutofillId[] ids = response.getFillDialogTriggerIds();
+        if (ids == null || !ArrayUtils.contains(ids, filledId)) {
+            return false;
+        }
+
+        final Drawable serviceIcon = getServiceIcon();
+
+        getUiForShowing().showFillDialog(filledId, response, filterText,
+                mService.getServicePackageName(), mComponentName, serviceIcon, this);
+        return true;
+    }
+
+    @SuppressWarnings("GuardedBy") // ErrorProne says we need to use mService.mLock, but it's
+                                   // actually the same object as mLock.
+                                   // TODO: Expose mService.mLock or redesign instead.
+    private Drawable getServiceIcon() {
+        synchronized (mLock) {
+            return mService.getServiceIconLocked();
+        }
+    }
+
     /**
      * Returns whether we made a request to show inline suggestions.
      */
@@ -3412,6 +3643,7 @@
 
     @GuardedBy("mLock")
     private void processNullResponseLocked(int requestId, int flags) {
+        unregisterDelayedFillBroadcastLocked();
         if ((flags & FLAG_MANUAL_REQUEST) != 0) {
             getUiForShowing().showError(R.string.autofill_error_cannot_autofill, this);
         }
@@ -3584,9 +3816,10 @@
                 mService.getRemoteInlineSuggestionRenderServiceLocked();
         if (remoteRenderService != null
                 && (mSessionFlags.mAugmentedAutofillOnly
-                || !mSessionFlags.mInlineSupportedByService
-                || mSessionFlags.mExpiredResponse)
-                && isViewFocusedLocked(flags)) {
+                        || !mSessionFlags.mInlineSupportedByService
+                        || mSessionFlags.mExpiredResponse)
+                && isViewFocusedLocked(flags)
+                || isFillDialogUiEnabled()) {
             if (sDebug) Slog.d(TAG, "Create inline request for augmented autofill");
             remoteRenderService.getInlineSuggestionsRendererInfo(new RemoteCallback(
                     (extras) -> {
@@ -3625,6 +3858,11 @@
         // only if handling the current response requires it.
         mUi.hideAll(this);
 
+        if ((newResponse.getFlags() & FillResponse.FLAG_DELAY_FILL) == 0) {
+            Slog.d(TAG, "Service did not request to wait for delayed fill response.");
+            unregisterDelayedFillBroadcastLocked();
+        }
+
         final int requestId = newResponse.getRequestId();
         if (sVerbose) {
             Slog.v(TAG, "processResponseLocked(): mCurrentViewId=" + mCurrentViewId
@@ -3642,6 +3880,7 @@
         mClientState = newClientState != null ? newClientState : newResponse.getClientState();
 
         setViewStatesLocked(newResponse, ViewState.STATE_FILLABLE, false);
+        updateFillDialogTriggerIdsLocked();
         updateTrackedIdsLocked();
 
         if (mCurrentViewId == null) {
@@ -4176,6 +4415,9 @@
             return null;
         }
 
+        clearPendingIntentLocked();
+        unregisterDelayedFillBroadcastLocked();
+
         unlinkClientVultureLocked();
         mUi.destroyAll(mPendingSaveUi, this, true);
         mUi.clearCallback(this);
diff --git a/services/autofill/java/com/android/server/autofill/ViewState.java b/services/autofill/java/com/android/server/autofill/ViewState.java
index adb1e3e..4a14f14 100644
--- a/services/autofill/java/com/android/server/autofill/ViewState.java
+++ b/services/autofill/java/com/android/server/autofill/ViewState.java
@@ -82,6 +82,8 @@
     public static final int STATE_INLINE_DISABLED = 0x8000;
     /** The View is waiting for an inline suggestions request from IME.*/
     public static final int STATE_PENDING_CREATE_INLINE_REQUEST = 0x10000;
+    /** Fill dialog were shown for this View. */
+    public static final int STATE_FILL_DIALOG_SHOWN = 0x20000;
 
     public final AutofillId id;
 
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index 71c3c16..056ab92 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -66,6 +66,7 @@
 
     private @Nullable FillUi mFillUi;
     private @Nullable SaveUi mSaveUi;
+    private @Nullable DialogFillUi mFillDialog;
 
     private @Nullable AutoFillUiCallback mCallback;
 
@@ -90,6 +91,7 @@
         void startIntentSender(IntentSender intentSender, Intent intent);
         void dispatchUnhandledKey(AutofillId id, KeyEvent keyEvent);
         void cancelSession();
+        void requestShowSoftInput(AutofillId id);
     }
 
     public AutoFillUI(@NonNull Context context) {
@@ -155,6 +157,12 @@
     }
 
     /**
+     * Hides the fill UI.
+     */
+    public void hideFillDialog(@NonNull AutoFillUiCallback callback) {
+        mHandler.post(() -> hideFillDialogUiThread(callback));
+    }
+    /**
      * Filters the options in the fill UI.
      *
      * @param filterText The filter prefix.
@@ -369,6 +377,62 @@
     }
 
     /**
+     * Shows the UI asking the user to choose for autofill.
+     */
+    public void showFillDialog(@NonNull AutofillId focusedId, @NonNull FillResponse response,
+            @Nullable String filterText, @Nullable String servicePackageName,
+            @NonNull ComponentName componentName, @Nullable Drawable serviceIcon,
+            @NonNull AutoFillUiCallback callback) {
+        if (sVerbose) {
+            Slog.v(TAG, "showFillDialog for "
+                    + componentName.toShortString() + ": " + response);
+        }
+
+        // TODO: enable LogMaker
+
+        mHandler.post(() -> {
+            if (callback != mCallback) {
+                return;
+            }
+            hideAllUiThread(callback);
+            mFillDialog = new DialogFillUi(mContext, response, focusedId, filterText,
+                    serviceIcon, servicePackageName, componentName, mOverlayControl,
+                    mUiModeMgr.isNightMode(), new DialogFillUi.UiCallback() {
+                        @Override
+                        public void onResponsePicked(FillResponse response) {
+                            hideFillDialogUiThread(callback);
+                            if (mCallback != null) {
+                                mCallback.authenticate(response.getRequestId(),
+                                        AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED,
+                                        response.getAuthentication(), response.getClientState(),
+                                        /* authenticateInline= */ false);
+                            }
+                        }
+
+                        @Override
+                        public void onDatasetPicked(Dataset dataset) {
+                            hideFillDialogUiThread(callback);
+                            if (mCallback != null) {
+                                final int datasetIndex = response.getDatasets().indexOf(dataset);
+                                mCallback.fill(response.getRequestId(), datasetIndex, dataset);
+                            }
+                        }
+
+                        @Override
+                        public void onCanceled() {
+                            hideFillDialogUiThread(callback);
+                            callback.requestShowSoftInput(focusedId);
+                        }
+
+                        @Override
+                        public void startIntentSender(IntentSender intentSender) {
+                            mCallback.startIntentSenderAndFinishSession(intentSender);
+                        }
+                    });
+        });
+    }
+
+    /**
      * Executes an operation in the pending save UI, if any.
      */
     public void onPendingSaveUi(int operation, @NonNull IBinder token) {
@@ -400,6 +464,10 @@
         return mSaveUi == null ? false : mSaveUi.isShowing();
     }
 
+    public boolean isFillDialogShowing() {
+        return mFillDialog == null ? false : mFillDialog.isShowing();
+    }
+
     public void dump(PrintWriter pw) {
         pw.println("Autofill UI");
         final String prefix = "  ";
@@ -417,6 +485,12 @@
         } else {
             pw.print(prefix); pw.println("showsSaveUi: false");
         }
+        if (mFillDialog != null) {
+            pw.print(prefix); pw.println("showsFillDialog: true");
+            mFillDialog.dump(pw, prefix2);
+        } else {
+            pw.print(prefix); pw.println("showsFillDialog: false");
+        }
     }
 
     @android.annotation.UiThread
@@ -442,6 +516,14 @@
     }
 
     @android.annotation.UiThread
+    private void hideFillDialogUiThread(@Nullable AutoFillUiCallback callback) {
+        if (mFillDialog != null && (callback == null || callback == mCallback)) {
+            mFillDialog.destroy();
+            mFillDialog = null;
+        }
+    }
+
+    @android.annotation.UiThread
     private void destroySaveUiUiThread(@Nullable PendingUi pendingSaveUi, boolean notifyClient) {
         if (mSaveUi == null) {
             // Calling destroySaveUiUiThread() twice is normal - it usually happens when the
@@ -475,12 +557,14 @@
     private void destroyAllUiThread(@Nullable PendingUi pendingSaveUi,
             @Nullable AutoFillUiCallback callback, boolean notifyClient) {
         hideFillUiUiThread(callback, notifyClient);
+        hideFillDialogUiThread(callback);
         destroySaveUiUiThread(pendingSaveUi, notifyClient);
     }
 
     @android.annotation.UiThread
     private void hideAllUiThread(@Nullable AutoFillUiCallback callback) {
         hideFillUiUiThread(callback, true);
+        hideFillDialogUiThread(callback);
         final PendingUi pendingSaveUi = hideSaveUiUiThread(callback);
         if (pendingSaveUi != null && pendingSaveUi.getState() == PendingUi.STATE_FINISHED) {
             if (sDebug) {
diff --git a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
new file mode 100644
index 0000000..e122993
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
@@ -0,0 +1,629 @@
+/*
+ * 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.autofill.ui;
+
+import static com.android.server.autofill.Helper.sDebug;
+import static com.android.server.autofill.Helper.sVerbose;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Dialog;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.IntentSender;
+import android.graphics.drawable.Drawable;
+import android.service.autofill.Dataset;
+import android.service.autofill.FillResponse;
+import android.text.TextUtils;
+import android.util.PluralsMessageFormatter;
+import android.util.Slog;
+import android.view.ContextThemeWrapper;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.Filter;
+import android.widget.Filterable;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+
+import com.android.internal.R;
+import com.android.server.autofill.AutofillManagerService;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * A dialog to show Autofill suggestions.
+ *
+ * This fill dialog UI shows as a bottom sheet style dialog. This dialog UI
+ * provides a larger area to display the suggestions, it provides a more
+ * conspicuous and efficient interface to the user. So it is easy for users
+ * to pay attention to the datasets and selecting one of them.
+ */
+final class DialogFillUi {
+
+    private static final String TAG = "DialogFillUi";
+    private static final int THEME_ID_LIGHT =
+            R.style.Theme_DeviceDefault_Light_Autofill_Save;
+    private static final int THEME_ID_DARK =
+            R.style.Theme_DeviceDefault_Autofill_Save;
+
+    interface UiCallback {
+        void onResponsePicked(@NonNull FillResponse response);
+        void onDatasetPicked(@NonNull Dataset dataset);
+        void onCanceled();
+        void startIntentSender(IntentSender intentSender);
+    }
+
+    private final @NonNull Dialog mDialog;
+    private final @NonNull OverlayControl mOverlayControl;
+    private final String mServicePackageName;
+    private final ComponentName mComponentName;
+    private final int mThemeId;
+    private final @NonNull Context mContext;
+    private final @NonNull UiCallback mCallback;
+    private final @NonNull ListView mListView;
+    private final @Nullable ItemsAdapter mAdapter;
+    private final int mVisibleDatasetsMaxCount;
+
+    private @Nullable String mFilterText;
+    private @Nullable AnnounceFilterResult mAnnounceFilterResult;
+    private boolean mDestroyed;
+
+    DialogFillUi(@NonNull Context context, @NonNull FillResponse response,
+            @NonNull AutofillId focusedViewId, @Nullable String filterText,
+            @Nullable Drawable serviceIcon, @Nullable String servicePackageName,
+            @Nullable ComponentName componentName, @NonNull OverlayControl overlayControl,
+            boolean nightMode, @NonNull UiCallback callback) {
+        if (sVerbose) Slog.v(TAG, "nightMode: " + nightMode);
+        mThemeId = nightMode ? THEME_ID_DARK : THEME_ID_LIGHT;
+        mCallback = callback;
+        mOverlayControl = overlayControl;
+        mServicePackageName = servicePackageName;
+        mComponentName = componentName;
+
+        mContext = new ContextThemeWrapper(context, mThemeId);
+        final LayoutInflater inflater = LayoutInflater.from(mContext);
+        final View decor = inflater.inflate(R.layout.autofill_fill_dialog, null);
+
+        setServiceIcon(decor, serviceIcon);
+        setHeader(decor, response);
+
+        mVisibleDatasetsMaxCount = getVisibleDatasetsMaxCount();
+
+        if (response.getAuthentication() != null) {
+            mListView = null;
+            mAdapter = null;
+            try {
+                initialAuthenticationLayout(decor, response);
+            } catch (RuntimeException e) {
+                callback.onCanceled();
+                Slog.e(TAG, "Error inflating remote views", e);
+                mDialog = null;
+                return;
+            }
+        } else {
+            final List<ViewItem> items = createDatasetItems(response, focusedViewId);
+            mAdapter = new ItemsAdapter(items);
+            mListView = decor.findViewById(R.id.autofill_dialog_list);
+            initialDatasetLayout(decor, filterText);
+        }
+
+        setDismissButton(decor);
+
+        mDialog = new Dialog(mContext, mThemeId);
+        mDialog.setContentView(decor);
+        setDialogParamsAsBottomSheet();
+
+        show();
+    }
+
+    private int getVisibleDatasetsMaxCount() {
+        if (AutofillManagerService.getVisibleDatasetsMaxCount() > 0) {
+            final int maxCount = AutofillManagerService.getVisibleDatasetsMaxCount();
+            if (sVerbose) {
+                Slog.v(TAG, "overriding maximum visible datasets to " + maxCount);
+            }
+            return maxCount;
+        } else {
+            return mContext.getResources()
+                    .getInteger(com.android.internal.R.integer.autofill_max_visible_datasets);
+        }
+    }
+
+    private void setDialogParamsAsBottomSheet() {
+        final Window window = mDialog.getWindow();
+        window.setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
+        window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
+                | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
+        window.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS);
+        window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
+        window.setGravity(Gravity.BOTTOM | Gravity.CENTER);
+        window.setCloseOnTouchOutside(true);
+        final WindowManager.LayoutParams params = window.getAttributes();
+        params.width = WindowManager.LayoutParams.MATCH_PARENT;
+        params.accessibilityTitle =
+                mContext.getString(R.string.autofill_picker_accessibility_title);
+        params.windowAnimations = R.style.AutofillSaveAnimation;
+    }
+
+    private void setServiceIcon(View decor, Drawable serviceIcon) {
+        if (serviceIcon == null) {
+            return;
+        }
+
+        final ImageView iconView = decor.findViewById(R.id.autofill_service_icon);
+        final int actualWidth = serviceIcon.getMinimumWidth();
+        final int actualHeight = serviceIcon.getMinimumHeight();
+        if (sDebug) {
+            Slog.d(TAG, "Adding service icon "
+                    + "(" + actualWidth + "x" + actualHeight + ")");
+        }
+        iconView.setImageDrawable(serviceIcon);
+        iconView.setVisibility(View.VISIBLE);
+    }
+
+    private void setHeader(View decor, FillResponse response) {
+        final RemoteViews presentation = response.getDialogHeader();
+        if (presentation == null) {
+            return;
+        }
+
+        final ViewGroup container = decor.findViewById(R.id.autofill_dialog_header);
+        final RemoteViews.InteractionHandler interceptionHandler = (view, pendingIntent, r) -> {
+            if (pendingIntent != null) {
+                mCallback.startIntentSender(pendingIntent.getIntentSender());
+            }
+            return true;
+        };
+
+        final View content = presentation.applyWithTheme(
+                mContext, (ViewGroup) decor, interceptionHandler, mThemeId);
+        container.addView(content);
+        container.setVisibility(View.VISIBLE);
+    }
+
+    private void setDismissButton(View decor) {
+        final TextView noButton = decor.findViewById(R.id.autofill_dialog_no);
+        noButton.setOnClickListener((v) -> mCallback.onCanceled());
+    }
+
+    private void setContinueButton(View decor, View.OnClickListener listener) {
+        final TextView yesButton = decor.findViewById(R.id.autofill_dialog_yes);
+        // set "Continue" by default
+        yesButton.setText(R.string.autofill_continue_yes);
+        yesButton.setOnClickListener(listener);
+    }
+
+    private void initialAuthenticationLayout(View decor, FillResponse response) {
+        RemoteViews presentation = response.getDialogPresentation();
+        if (presentation == null) {
+            presentation = response.getPresentation();
+        }
+        if (presentation == null) {
+            throw new RuntimeException("No presentation for fill dialog authentication");
+        }
+
+        // insert authentication item under autofill_dialog_container
+        final ViewGroup container = decor.findViewById(R.id.autofill_dialog_container);
+        final RemoteViews.InteractionHandler interceptionHandler = (view, pendingIntent, r) -> {
+            if (pendingIntent != null) {
+                mCallback.startIntentSender(pendingIntent.getIntentSender());
+            }
+            return true;
+        };
+        final View content = presentation.applyWithTheme(
+                mContext, (ViewGroup) decor, interceptionHandler, mThemeId);
+        container.addView(content);
+        container.setVisibility(View.VISIBLE);
+        container.setFocusable(true);
+        container.setOnClickListener(v -> mCallback.onResponsePicked(response));
+        // just single item, set up continue button
+        setContinueButton(decor, v -> mCallback.onResponsePicked(response));
+    }
+
+    private ArrayList<ViewItem> createDatasetItems(FillResponse response,
+            AutofillId focusedViewId) {
+        final int datasetCount = response.getDatasets().size();
+        if (sVerbose) {
+            Slog.v(TAG, "Number datasets: " + datasetCount + " max visible: "
+                    + mVisibleDatasetsMaxCount);
+        }
+
+        final RemoteViews.InteractionHandler interceptionHandler = (view, pendingIntent, r) -> {
+            if (pendingIntent != null) {
+                mCallback.startIntentSender(pendingIntent.getIntentSender());
+            }
+            return true;
+        };
+
+        final ArrayList<ViewItem> items = new ArrayList<>(datasetCount);
+        for (int i = 0; i < datasetCount; i++) {
+            final Dataset dataset = response.getDatasets().get(i);
+            final int index = dataset.getFieldIds().indexOf(focusedViewId);
+            if (index >= 0) {
+                RemoteViews presentation = dataset.getFieldDialogPresentation(index);
+                if (presentation == null) {
+                    Slog.w(TAG, "fallback to presentation");
+                    presentation = dataset.getFieldPresentation(index);
+                }
+                if (presentation == null) {
+                    Slog.w(TAG, "not displaying UI on field " + focusedViewId + " because "
+                            + "service didn't provide a presentation for it on " + dataset);
+                    continue;
+                }
+                final View view;
+                try {
+                    if (sVerbose) Slog.v(TAG, "setting remote view for " + focusedViewId);
+                    view = presentation.applyWithTheme(
+                            mContext, null, interceptionHandler, mThemeId);
+                } catch (RuntimeException e) {
+                    Slog.e(TAG, "Error inflating remote views", e);
+                    continue;
+                }
+                // TODO: Extract the shared filtering logic here and in FillUi to a common
+                //  method.
+                final Dataset.DatasetFieldFilter filter = dataset.getFilter(index);
+                Pattern filterPattern = null;
+                String valueText = null;
+                boolean filterable = true;
+                if (filter == null) {
+                    final AutofillValue value = dataset.getFieldValues().get(index);
+                    if (value != null && value.isText()) {
+                        valueText = value.getTextValue().toString().toLowerCase();
+                    }
+                } else {
+                    filterPattern = filter.pattern;
+                    if (filterPattern == null) {
+                        if (sVerbose) {
+                            Slog.v(TAG, "Explicitly disabling filter at id " + focusedViewId
+                                    + " for dataset #" + index);
+                        }
+                        filterable = false;
+                    }
+                }
+
+                items.add(new ViewItem(dataset, filterPattern, filterable, valueText, view));
+            }
+        }
+        return items;
+    }
+
+    private void initialDatasetLayout(View decor, String filterText) {
+        final AdapterView.OnItemClickListener onItemClickListener =
+                (adapter, view, position, id) -> {
+                    final ViewItem vi = mAdapter.getItem(position);
+                    mCallback.onDatasetPicked(vi.dataset);
+                };
+
+        mListView.setAdapter(mAdapter);
+        mListView.setVisibility(View.VISIBLE);
+        mListView.setOnItemClickListener(onItemClickListener);
+
+        if (mAdapter.getCount() == 1) {
+            // just single item, set up continue button
+            setContinueButton(decor, (v) ->
+                    onItemClickListener.onItemClick(null, null, 0, 0));
+        }
+
+        if (filterText == null) {
+            mFilterText = null;
+        } else {
+            mFilterText = filterText.toLowerCase();
+        }
+
+        final int oldCount = mAdapter.getCount();
+        mAdapter.getFilter().filter(mFilterText, (count) -> {
+            if (mDestroyed) {
+                return;
+            }
+            if (count <= 0) {
+                if (sDebug) {
+                    final int size = mFilterText == null ? 0 : mFilterText.length();
+                    Slog.d(TAG, "No dataset matches filter with " + size + " chars");
+                }
+                mCallback.onCanceled();
+            } else {
+
+                if (mAdapter.getCount() > mVisibleDatasetsMaxCount) {
+                    mListView.setVerticalScrollBarEnabled(true);
+                    mListView.onVisibilityAggregated(true);
+                } else {
+                    mListView.setVerticalScrollBarEnabled(false);
+                }
+                if (mAdapter.getCount() != oldCount) {
+                    mListView.requestLayout();
+                }
+            }
+        });
+    }
+
+    private void show() {
+        Slog.i(TAG, "Showing fill dialog");
+        mDialog.show();
+        mOverlayControl.hideOverlays();
+    }
+
+    boolean isShowing() {
+        return mDialog.isShowing();
+    }
+
+    void hide() {
+        if (sVerbose) Slog.v(TAG, "Hiding fill dialog.");
+        try {
+            mDialog.hide();
+        } finally {
+            mOverlayControl.showOverlays();
+        }
+    }
+
+    void destroy() {
+        try {
+            if (sDebug) Slog.d(TAG, "destroy()");
+            throwIfDestroyed();
+
+            mDialog.dismiss();
+            mDestroyed = true;
+        } finally {
+            mOverlayControl.showOverlays();
+        }
+    }
+
+    private void throwIfDestroyed() {
+        if (mDestroyed) {
+            throw new IllegalStateException("cannot interact with a destroyed instance");
+        }
+    }
+
+    @Override
+    public String toString() {
+        // TODO toString
+        return "NO TITLE";
+    }
+
+    void dump(PrintWriter pw, String prefix) {
+
+        pw.print(prefix); pw.print("service: "); pw.println(mServicePackageName);
+        pw.print(prefix); pw.print("app: "); pw.println(mComponentName.toShortString());
+        pw.print(prefix); pw.print("theme id: "); pw.print(mThemeId);
+        switch (mThemeId) {
+            case THEME_ID_DARK:
+                pw.println(" (dark)");
+                break;
+            case THEME_ID_LIGHT:
+                pw.println(" (light)");
+                break;
+            default:
+                pw.println("(UNKNOWN_MODE)");
+                break;
+        }
+        final View view = mDialog.getWindow().getDecorView();
+        final int[] loc = view.getLocationOnScreen();
+        pw.print(prefix); pw.print("coordinates: ");
+            pw.print('('); pw.print(loc[0]); pw.print(','); pw.print(loc[1]); pw.print(')');
+            pw.print('(');
+                pw.print(loc[0] + view.getWidth()); pw.print(',');
+                pw.print(loc[1] + view.getHeight()); pw.println(')');
+        pw.print(prefix); pw.print("destroyed: "); pw.println(mDestroyed);
+    }
+
+    private void announceSearchResultIfNeeded() {
+        if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+            if (mAnnounceFilterResult == null) {
+                mAnnounceFilterResult = new AnnounceFilterResult();
+            }
+            mAnnounceFilterResult.post();
+        }
+    }
+
+    // TODO: Below code copied from FullUi, Extract the shared filtering logic here
+    // and in FillUi to a common method.
+    private final class AnnounceFilterResult implements Runnable {
+        private static final int SEARCH_RESULT_ANNOUNCEMENT_DELAY = 1000; // 1 sec
+
+        public void post() {
+            remove();
+            mListView.postDelayed(this, SEARCH_RESULT_ANNOUNCEMENT_DELAY);
+        }
+
+        public void remove() {
+            mListView.removeCallbacks(this);
+        }
+
+        @Override
+        public void run() {
+            final int count = mListView.getAdapter().getCount();
+            final String text;
+            if (count <= 0) {
+                text = mContext.getString(R.string.autofill_picker_no_suggestions);
+            } else {
+                Map<String, Object> arguments = new HashMap<>();
+                arguments.put("count", count);
+                text = PluralsMessageFormatter.format(mContext.getResources(),
+                        arguments,
+                        R.string.autofill_picker_some_suggestions);
+            }
+            mListView.announceForAccessibility(text);
+        }
+    }
+
+    private final class ItemsAdapter extends BaseAdapter implements Filterable {
+        private @NonNull final List<ViewItem> mAllItems;
+
+        private @NonNull final List<ViewItem> mFilteredItems = new ArrayList<>();
+
+        ItemsAdapter(@NonNull List<ViewItem> items) {
+            mAllItems = Collections.unmodifiableList(new ArrayList<>(items));
+            mFilteredItems.addAll(items);
+        }
+
+        @Override
+        public Filter getFilter() {
+            return new Filter() {
+                @Override
+                protected FilterResults performFiltering(CharSequence filterText) {
+                    // No locking needed as mAllItems is final an immutable
+                    final List<ViewItem> filtered = mAllItems.stream()
+                            .filter((item) -> item.matches(filterText))
+                            .collect(Collectors.toList());
+                    final FilterResults results = new FilterResults();
+                    results.values = filtered;
+                    results.count = filtered.size();
+                    return results;
+                }
+
+                @Override
+                protected void publishResults(CharSequence constraint, FilterResults results) {
+                    final boolean resultCountChanged;
+                    final int oldItemCount = mFilteredItems.size();
+                    mFilteredItems.clear();
+                    if (results.count > 0) {
+                        @SuppressWarnings("unchecked") final List<ViewItem> items =
+                                (List<ViewItem>) results.values;
+                        mFilteredItems.addAll(items);
+                    }
+                    resultCountChanged = (oldItemCount != mFilteredItems.size());
+                    if (resultCountChanged) {
+                        announceSearchResultIfNeeded();
+                    }
+                    notifyDataSetChanged();
+                }
+            };
+        }
+
+        @Override
+        public int getCount() {
+            return mFilteredItems.size();
+        }
+
+        @Override
+        public ViewItem getItem(int position) {
+            return mFilteredItems.get(position);
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return position;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            return getItem(position).view;
+        }
+
+        @Override
+        public String toString() {
+            return "ItemsAdapter: [all=" + mAllItems + ", filtered=" + mFilteredItems + "]";
+        }
+    }
+
+
+    /**
+     * An item for the list view - either a (clickable) dataset or a (read-only) header / footer.
+     */
+    private static class ViewItem {
+        public final @Nullable String value;
+        public final @Nullable Dataset dataset;
+        public final @NonNull View view;
+        public final @Nullable Pattern filter;
+        public final boolean filterable;
+
+        /**
+         * Default constructor.
+         *
+         * @param dataset dataset associated with the item
+         * @param filter optional filter set by the service to determine how the item should be
+         * filtered
+         * @param filterable optional flag set by the service to indicate this item should not be
+         * filtered (typically used when the dataset has value but it's sensitive, like a password)
+         * @param value dataset value
+         * @param view dataset presentation.
+         */
+        ViewItem(@NonNull Dataset dataset, @Nullable Pattern filter, boolean filterable,
+                @Nullable String value, @NonNull View view) {
+            this.dataset = dataset;
+            this.value = value;
+            this.view = view;
+            this.filter = filter;
+            this.filterable = filterable;
+        }
+
+        /**
+         * Returns whether this item matches the value input by the user so it can be included
+         * in the filtered datasets.
+         */
+        public boolean matches(CharSequence filterText) {
+            if (TextUtils.isEmpty(filterText)) {
+                // Always show item when the user input is empty
+                return true;
+            }
+            if (!filterable) {
+                // Service explicitly disabled filtering using a null Pattern.
+                return false;
+            }
+            final String constraintLowerCase = filterText.toString().toLowerCase();
+            if (filter != null) {
+                // Uses pattern provided by service
+                return filter.matcher(constraintLowerCase).matches();
+            } else {
+                // Compares it with dataset value with dataset
+                return (value == null)
+                        ? (dataset.getAuthentication() == null)
+                        : value.toLowerCase().startsWith(constraintLowerCase);
+            }
+        }
+
+        @Override
+        public String toString() {
+            final StringBuilder builder = new StringBuilder("ViewItem:[view=")
+                    .append(view.getAutofillId());
+            final String datasetId = dataset == null ? null : dataset.getId();
+            if (datasetId != null) {
+                builder.append(", dataset=").append(datasetId);
+            }
+            if (value != null) {
+                // Cannot print value because it could contain PII
+                builder.append(", value=").append(value.length()).append("_chars");
+            }
+            if (filterable) {
+                builder.append(", filterable");
+            }
+            if (filter != null) {
+                // Filter should not have PII, but it could be a huge regexp
+                builder.append(", filter=").append(filter.pattern().length()).append("_chars");
+            }
+            return builder.append(']').toString();
+        }
+    }
+}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index cfd3798..c3ab2a7 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -1255,7 +1255,7 @@
         }
 
         @Override
-        public void onDeviceDisconnected(BluetoothDevice device, @DisconnectReason int reason) {
+        public void onDeviceDisconnected(BluetoothDevice device, int reason) {
             Slog.d(LOG_TAG, device.getAddress() + " disconnected w/ reason: (" + reason + ") "
                     + BluetoothAdapter.BluetoothConnectionCallback.disconnectReasonText(reason));
             CompanionDeviceManagerService.this.onDeviceDisconnected(device.getAddress());
diff --git a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
index dbe866b..93cbe97 100644
--- a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
+++ b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
@@ -91,7 +91,7 @@
      */
     @Override
     public void onDeviceDisconnected(@NonNull BluetoothDevice device,
-            @DisconnectReason int reason) {
+            int reason) {
         if (DEBUG) {
             Log.i(TAG, "onDevice_Disconnected() " + btDeviceToString(device));
             Log.d(TAG, "  reason=" + disconnectReasonText(reason));
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index 6c56e2f..e6bfd1f 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -18,8 +18,11 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.StringDef;
 import android.graphics.Point;
 import android.graphics.PointF;
+import android.hardware.display.DisplayManagerInternal;
+import android.hardware.input.InputManager;
 import android.hardware.input.InputManagerInternal;
 import android.hardware.input.VirtualKeyEvent;
 import android.hardware.input.VirtualMouseButtonEvent;
@@ -48,6 +51,20 @@
 
     private static final String TAG = "VirtualInputController";
 
+    private static final AtomicLong sNextPhysId = new AtomicLong(1);
+
+    static final String PHYS_TYPE_KEYBOARD = "Keyboard";
+    static final String PHYS_TYPE_MOUSE = "Mouse";
+    static final String PHYS_TYPE_TOUCHSCREEN = "Touchscreen";
+    @StringDef(prefix = { "PHYS_TYPE_" }, value = {
+            PHYS_TYPE_KEYBOARD,
+            PHYS_TYPE_MOUSE,
+            PHYS_TYPE_TOUCHSCREEN,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface PhysType {
+    }
+
     private final Object mLock;
 
     /* Token -> file descriptor associations. */
@@ -56,6 +73,8 @@
     final Map<IBinder, InputDeviceDescriptor> mInputDeviceDescriptors = new ArrayMap<>();
 
     private final NativeWrapper mNativeWrapper;
+    private final DisplayManagerInternal mDisplayManagerInternal;
+    private final InputManagerInternal mInputManagerInternal;
 
     /**
      * Because the pointer is a singleton, it can only be targeted at one display at a time. Because
@@ -73,6 +92,8 @@
         mLock = lock;
         mNativeWrapper = nativeWrapper;
         mActivePointerDisplayId = Display.INVALID_DISPLAY;
+        mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
+        mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
     }
 
     void close() {
@@ -90,7 +111,9 @@
             int productId,
             @NonNull IBinder deviceToken,
             int displayId) {
-        final int fd = mNativeWrapper.openUinputKeyboard(deviceName, vendorId, productId);
+        final String phys = createPhys(PHYS_TYPE_KEYBOARD);
+        setUniqueIdAssociation(displayId, phys);
+        final int fd = mNativeWrapper.openUinputKeyboard(deviceName, vendorId, productId, phys);
         if (fd < 0) {
             throw new RuntimeException(
                     "A native error occurred when creating keyboard: " + -fd);
@@ -99,7 +122,7 @@
         synchronized (mLock) {
             mInputDeviceDescriptors.put(deviceToken,
                     new InputDeviceDescriptor(fd, binderDeathRecipient,
-                            InputDeviceDescriptor.TYPE_KEYBOARD, displayId));
+                            InputDeviceDescriptor.TYPE_KEYBOARD, displayId, phys));
         }
         try {
             deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
@@ -114,7 +137,9 @@
             int productId,
             @NonNull IBinder deviceToken,
             int displayId) {
-        final int fd = mNativeWrapper.openUinputMouse(deviceName, vendorId, productId);
+        final String phys = createPhys(PHYS_TYPE_MOUSE);
+        setUniqueIdAssociation(displayId, phys);
+        final int fd = mNativeWrapper.openUinputMouse(deviceName, vendorId, productId, phys);
         if (fd < 0) {
             throw new RuntimeException(
                     "A native error occurred when creating mouse: " + -fd);
@@ -123,11 +148,9 @@
         synchronized (mLock) {
             mInputDeviceDescriptors.put(deviceToken,
                     new InputDeviceDescriptor(fd, binderDeathRecipient,
-                            InputDeviceDescriptor.TYPE_MOUSE, displayId));
-            final InputManagerInternal inputManagerInternal =
-                    LocalServices.getService(InputManagerInternal.class);
-            inputManagerInternal.setVirtualMousePointerDisplayId(displayId);
-            inputManagerInternal.setPointerAcceleration(1);
+                            InputDeviceDescriptor.TYPE_MOUSE, displayId, phys));
+            mInputManagerInternal.setVirtualMousePointerDisplayId(displayId);
+            mInputManagerInternal.setPointerAcceleration(1);
             mActivePointerDisplayId = displayId;
         }
         try {
@@ -144,7 +167,9 @@
             @NonNull IBinder deviceToken,
             int displayId,
             @NonNull Point screenSize) {
-        final int fd = mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId,
+        final String phys = createPhys(PHYS_TYPE_TOUCHSCREEN);
+        setUniqueIdAssociation(displayId, phys);
+        final int fd = mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId, phys,
                 screenSize.y, screenSize.x);
         if (fd < 0) {
             throw new RuntimeException(
@@ -154,7 +179,7 @@
         synchronized (mLock) {
             mInputDeviceDescriptors.put(deviceToken,
                     new InputDeviceDescriptor(fd, binderDeathRecipient,
-                            InputDeviceDescriptor.TYPE_TOUCHSCREEN, displayId));
+                            InputDeviceDescriptor.TYPE_TOUCHSCREEN, displayId, phys));
         }
         try {
             deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
@@ -174,6 +199,7 @@
             }
             token.unlinkToDeath(inputDeviceDescriptor.getDeathRecipient(), /* flags= */ 0);
             mNativeWrapper.closeUinput(inputDeviceDescriptor.getFileDescriptor());
+            InputManager.getInstance().removeUniqueIdAssociation(inputDeviceDescriptor.getPhys());
 
             // Reset values to the default if all virtual mice are unregistered, or set display
             // id if there's another mouse (choose the most recent).
@@ -197,9 +223,7 @@
             }
         }
         if (mostRecentlyCreatedMouse != null) {
-            final InputManagerInternal inputManagerInternal =
-                    LocalServices.getService(InputManagerInternal.class);
-            inputManagerInternal.setVirtualMousePointerDisplayId(
+            mInputManagerInternal.setVirtualMousePointerDisplayId(
                     mostRecentlyCreatedMouse.getDisplayId());
             mActivePointerDisplayId = mostRecentlyCreatedMouse.getDisplayId();
         } else {
@@ -209,14 +233,21 @@
     }
 
     private void resetMouseValuesLocked() {
-        final InputManagerInternal inputManagerInternal =
-                LocalServices.getService(InputManagerInternal.class);
-        inputManagerInternal.setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY);
-        inputManagerInternal.setPointerAcceleration(
+        mInputManagerInternal.setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY);
+        mInputManagerInternal.setPointerAcceleration(
                 IInputConstants.DEFAULT_POINTER_ACCELERATION);
         mActivePointerDisplayId = Display.INVALID_DISPLAY;
     }
 
+    private static String createPhys(@PhysType String type) {
+        return String.format("virtual%s:%d", type, sNextPhysId.getAndIncrement());
+    }
+
+    private void setUniqueIdAssociation(int displayId, String phys) {
+        final String displayUniqueId = mDisplayManagerInternal.getDisplayInfo(displayId).uniqueId;
+        InputManager.getInstance().addUniqueIdAssociation(phys, displayUniqueId);
+    }
+
     boolean sendKeyEvent(@NonNull IBinder token, @NonNull VirtualKeyEvent event) {
         synchronized (mLock) {
             final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get(
@@ -321,17 +352,18 @@
                 fout.println("          creationOrder: "
                         + inputDeviceDescriptor.getCreationOrderNumber());
                 fout.println("          type: " + inputDeviceDescriptor.getType());
+                fout.println("          phys: " + inputDeviceDescriptor.getPhys());
             }
             fout.println("      Active mouse display id: " + mActivePointerDisplayId);
         }
     }
 
     private static native int nativeOpenUinputKeyboard(String deviceName, int vendorId,
-            int productId);
-    private static native int nativeOpenUinputMouse(String deviceName, int vendorId,
-            int productId);
+            int productId, String phys);
+    private static native int nativeOpenUinputMouse(String deviceName, int vendorId, int productId,
+            String phys);
     private static native int nativeOpenUinputTouchscreen(String deviceName, int vendorId,
-            int productId, int height, int width);
+            int productId, String phys, int height, int width);
     private static native boolean nativeCloseUinput(int fd);
     private static native boolean nativeWriteKeyEvent(int fd, int androidKeyCode, int action);
     private static native boolean nativeWriteButtonEvent(int fd, int buttonCode, int action);
@@ -345,20 +377,18 @@
     /** Wrapper around the static native methods for tests. */
     @VisibleForTesting
     protected static class NativeWrapper {
-        public int openUinputKeyboard(String deviceName, int vendorId, int productId) {
-            return nativeOpenUinputKeyboard(deviceName, vendorId,
-                    productId);
+        public int openUinputKeyboard(String deviceName, int vendorId, int productId, String phys) {
+            return nativeOpenUinputKeyboard(deviceName, vendorId, productId, phys);
         }
 
-        public int openUinputMouse(String deviceName, int vendorId, int productId) {
-            return nativeOpenUinputMouse(deviceName, vendorId,
-                    productId);
+        public int openUinputMouse(String deviceName, int vendorId, int productId, String phys) {
+            return nativeOpenUinputMouse(deviceName, vendorId, productId, phys);
         }
 
-        public int openUinputTouchscreen(String deviceName, int vendorId, int productId, int height,
-                int width) {
-            return nativeOpenUinputTouchscreen(deviceName, vendorId,
-                    productId, height, width);
+        public int openUinputTouchscreen(String deviceName, int vendorId,
+                int productId, String phys, int height, int width) {
+            return nativeOpenUinputTouchscreen(deviceName, vendorId, productId, phys, height,
+                    width);
         }
 
         public boolean closeUinput(int fd) {
@@ -410,15 +440,17 @@
         private final IBinder.DeathRecipient mDeathRecipient;
         private final @Type int mType;
         private final int mDisplayId;
+        private final String mPhys;
         // Monotonically increasing number; devices with lower numbers were created earlier.
         private final long mCreationOrderNumber;
 
-        InputDeviceDescriptor(int fd, IBinder.DeathRecipient deathRecipient,
-                @Type int type, int displayId) {
+        InputDeviceDescriptor(int fd, IBinder.DeathRecipient deathRecipient, @Type int type,
+                int displayId, String phys) {
             mFd = fd;
             mDeathRecipient = deathRecipient;
             mType = type;
             mDisplayId = displayId;
+            mPhys = phys;
             mCreationOrderNumber = sNextCreationOrderNumber.getAndIncrement();
         }
 
@@ -445,6 +477,10 @@
         public long getCreationOrderNumber() {
             return mCreationOrderNumber;
         }
+
+        public String getPhys() {
+            return mPhys;
+        }
     }
 
     private final class BinderDeathRecipient implements IBinder.DeathRecipient {
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index 16645df..06c11fa 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -673,6 +673,12 @@
                 throw new UnsupportedOperationException("cannot read frp credential");
             }
         }
+
+        @Override
+        public String getPersistentDataPackageName() {
+            enforcePersistentDataBlockAccess();
+            return mContext.getString(R.string.config_persistentDataPackageName);
+        }
     };
 
     private PersistentDataBlockManagerInternal mInternalService =
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index f71f02a..8aeae6a 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -123,6 +123,7 @@
 import android.provider.Downloads;
 import android.provider.MediaStore;
 import android.provider.Settings;
+import android.service.storage.ExternalStorageService;
 import android.sysprop.VoldProperties;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
@@ -491,6 +492,8 @@
     @GuardedBy("mAppFuseLock")
     private AppFuseBridge mAppFuseBridge = null;
 
+    private HashMap<Integer, Integer> mUserSharesMediaWith = new HashMap<>();
+
     /** Matches known application dir paths. The first group contains the generic part of the path,
      * the second group contains the user id (or null if it's a public volume without users), the
      * third group contains the package name, and the fourth group the remainder of the path.
@@ -1235,6 +1238,21 @@
     private void onUnlockUser(int userId) {
         Slog.d(TAG, "onUnlockUser " + userId);
 
+        if (userId != UserHandle.USER_SYSTEM) {
+            // Check if this user shares media with another user
+            try {
+                Context userContext = mContext.createPackageContextAsUser("system", 0,
+                        UserHandle.of(userId));
+                UserManager um = userContext.getSystemService(UserManager.class);
+                if (um != null && um.isMediaSharedWithParent()) {
+                    int parentUserId = um.getProfileParent(userId).id;
+                    mUserSharesMediaWith.put(userId, parentUserId);
+                    mUserSharesMediaWith.put(parentUserId, userId);
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+                Log.e(TAG, "Failed to create user context for user " + userId);
+            }
+        }
         // We purposefully block here to make sure that user-specific
         // staging area is ready so it's ready for zygote-forked apps to
         // bind mount against.
@@ -3971,6 +3989,29 @@
         final boolean realState = (flags & StorageManager.FLAG_REAL_STATE) != 0;
         final boolean includeInvisible = (flags & StorageManager.FLAG_INCLUDE_INVISIBLE) != 0;
         final boolean includeRecent = (flags & StorageManager.FLAG_INCLUDE_RECENT) != 0;
+        final boolean includeSharedProfile =
+                (flags & StorageManager.FLAG_INCLUDE_SHARED_PROFILE) != 0;
+
+        // Only Apps with MANAGE_EXTERNAL_STORAGE should call the API with includeSharedProfile
+        if (includeSharedProfile) {
+            try {
+                // Get package name for calling app and
+                // verify it has MANAGE_EXTERNAL_STORAGE permission
+                final String[] packagesFromUid = mIPackageManager.getPackagesForUid(callingUid);
+                if (packagesFromUid == null) {
+                    throw new SecurityException("Unknown uid " + callingUid);
+                }
+                // Checking first entry in packagesFromUid is enough as using "sharedUserId"
+                // mechanism is rare and discouraged. Also, Apps that share same UID share the same
+                // permissions.
+                if (!mStorageManagerInternal.hasExternalStorageAccess(callingUid,
+                        packagesFromUid[0])) {
+                    throw new SecurityException("Only File Manager Apps permitted");
+                }
+            } catch (RemoteException re) {
+                throw new SecurityException("Unknown uid " + callingUid, re);
+            }
+        }
 
         // Report all volumes as unmounted until we've recorded that user 0 has unlocked. There
         // are no guarantees that callers will see a consistent view of the volume before that
@@ -4002,6 +4043,7 @@
 
         final ArrayList<StorageVolume> res = new ArrayList<>();
         final ArraySet<String> resUuids = new ArraySet<>();
+        final int userIdSharingMedia = mUserSharesMediaWith.getOrDefault(userId, -1);
         synchronized (mLock) {
             for (int i = 0; i < mVolumes.size(); i++) {
                 final String volId = mVolumes.keyAt(i);
@@ -4014,6 +4056,11 @@
                         if (vol.getMountUserId() == userId) {
                             break;
                         }
+                        if (includeSharedProfile && vol.getMountUserId() == userIdSharingMedia) {
+                            // If the volume belongs to a user we share media with,
+                            // return it too.
+                            break;
+                        }
                         // Skip if emulated volume not for userId
                     default:
                         continue;
@@ -4021,10 +4068,12 @@
 
                 boolean match = false;
                 if (forWrite) {
-                    match = vol.isVisibleForWrite(userId);
+                    match = vol.isVisibleForWrite(userId)
+                            || (includeSharedProfile && vol.isVisibleForWrite(userIdSharingMedia));
                 } else {
                     match = vol.isVisibleForUser(userId)
-                            || (includeInvisible && vol.getPath() != null);
+                            || (includeInvisible && vol.getPath() != null)
+                            || (includeSharedProfile && vol.isVisibleForRead(userIdSharingMedia));
                 }
                 if (!match) continue;
 
@@ -4045,9 +4094,13 @@
                     reportUnmounted = true;
                 }
 
-                final StorageVolume userVol = vol.buildStorageVolume(mContext, userId,
+                int volUserId = userId;
+                if (volUserId != vol.getMountUserId() && vol.getMountUserId() >= 0) {
+                    volUserId = vol.getMountUserId();
+                }
+                final StorageVolume userVol = vol.buildStorageVolume(mContext, volUserId,
                         reportUnmounted);
-                if (vol.isPrimary()) {
+                if (vol.isPrimary() && vol.getMountUserId() == userId) {
                     res.add(0, userVol);
                     foundPrimary = true;
                 } else {
diff --git a/services/core/java/com/android/server/am/AppBatteryTracker.java b/services/core/java/com/android/server/am/AppBatteryTracker.java
index 8a21a0f..14d73f6 100644
--- a/services/core/java/com/android/server/am/AppBatteryTracker.java
+++ b/services/core/java/com/android/server/am/AppBatteryTracker.java
@@ -145,7 +145,7 @@
     /**
      * The uid battery usage stats data from our last query, it does not include snapshot data.
      */
-    // No lock is needed.
+    @GuardedBy("mLock")
     private final SparseDoubleArray mLastUidBatteryUsage = new SparseDoubleArray();
 
     // No lock is needed.
@@ -155,12 +155,15 @@
     private final SparseDoubleArray mTmpUidBatteryUsage2 = new SparseDoubleArray();
 
     // No lock is needed.
+    private final SparseDoubleArray mTmpUidBatteryUsageInWindow = new SparseDoubleArray();
+
+    // No lock is needed.
     private final ArraySet<UserHandle> mTmpUserIds = new ArraySet<>();
 
     /**
      * The start timestamp of the battery usage stats result from our last query.
      */
-    // No lock is needed.
+    @GuardedBy("mLock")
     private long mLastUidBatteryUsageStartTs;
 
     // For debug only.
@@ -296,8 +299,10 @@
             checkBatteryUsageStats();
         } else {
             // We didn't do the battery stats update above, schedule a check later.
-            scheduleBatteryUsageStatsUpdateIfNecessary(
-                    mLastBatteryUsageSamplingTs + mBatteryUsageStatsPollingMinIntervalMs - now);
+            synchronized (mLock) {
+                scheduleBatteryUsageStatsUpdateIfNecessary(
+                        mLastBatteryUsageSamplingTs + mBatteryUsageStatsPollingMinIntervalMs - now);
+            }
         }
     }
 
@@ -305,7 +310,10 @@
         final long now = SystemClock.elapsedRealtime();
         final AppBatteryPolicy bgPolicy = mInjector.getPolicy();
         try {
-            final SparseDoubleArray uidConsumers = mUidBatteryUsageInWindow;
+            final SparseDoubleArray uidConsumers = mTmpUidBatteryUsageInWindow;
+            synchronized (mLock) {
+                copyUidBatteryUsage(mUidBatteryUsageInWindow, uidConsumers);
+            }
             final long since = Math.max(0, now - bgPolicy.mBgCurrentDrainWindowMs);
             for (int i = 0, size = uidConsumers.size(); i < size; i++) {
                 final int uid = uidConsumers.keyAt(i);
@@ -408,7 +416,9 @@
 
         if (curDuration >= windowSize) {
             // If we do have long enough data for the window, save it.
-            copyUidBatteryUsage(buf, mUidBatteryUsageInWindow, windowSize * 1.0d / curDuration);
+            synchronized (mLock) {
+                copyUidBatteryUsage(buf, mUidBatteryUsageInWindow, windowSize * 1.0d / curDuration);
+            }
             needUpdateUidBatteryUsageInWindow = false;
         }
 
@@ -416,8 +426,11 @@
         mTmpUidBatteryUsage2.clear();
         copyUidBatteryUsage(buf, mTmpUidBatteryUsage2);
 
-        final long lastUidBatteryUsageStartTs = mLastUidBatteryUsageStartTs;
-        mLastUidBatteryUsageStartTs = curStart;
+        final long lastUidBatteryUsageStartTs;
+        synchronized (mLock) {
+            lastUidBatteryUsageStartTs = mLastUidBatteryUsageStartTs;
+            mLastUidBatteryUsageStartTs = curStart;
+        }
         if (curStart > lastUidBatteryUsageStartTs && lastUidBatteryUsageStartTs > 0) {
             // The battery usage stats committed data since our last query,
             // let's query the snapshots to get the data since last start.
@@ -429,42 +442,47 @@
         }
         if (needUpdateUidBatteryUsageInWindow && curDuration > windowSize) {
             // If we do have long enough data for the window, save it.
-            copyUidBatteryUsage(buf, mUidBatteryUsageInWindow, windowSize * 1.0d / curDuration);
+            synchronized (mLock) {
+                copyUidBatteryUsage(buf, mUidBatteryUsageInWindow, windowSize * 1.0d / curDuration);
+            }
             needUpdateUidBatteryUsageInWindow = false;
         }
 
         // Add the delta into the global records.
-        for (int i = 0, size = buf.size(); i < size; i++) {
-            final int uid = buf.keyAt(i);
-            final int index = mUidBatteryUsage.indexOfKey(uid);
-            final double delta = Math.max(0.0d,
-                    buf.valueAt(i) - mLastUidBatteryUsage.get(uid, 0.0d));
-            final double before;
-            if (index >= 0) {
-                before = mUidBatteryUsage.valueAt(index);
-                mUidBatteryUsage.setValueAt(index, before + delta);
-            } else {
-                before = 0.0d;
-                mUidBatteryUsage.put(uid, delta);
-            }
-            if (DEBUG_BACKGROUND_BATTERY_TRACKER) {
-                final double actualDelta = buf.valueAt(i) - mLastUidBatteryUsage.get(uid, 0.0d);
-                String msg = "Updating mUidBatteryUsage uid=" + uid + ", before=" + before
-                        + ", after=" + mUidBatteryUsage.get(uid, 0.0d) + ", delta=" + actualDelta
-                        + ", last=" + mLastUidBatteryUsage.get(uid, 0.0d)
-                        + ", curStart=" + curStart
-                        + ", lastLastStart=" + lastUidBatteryUsageStartTs
-                        + ", thisLastStart=" + mLastUidBatteryUsageStartTs;
-                if (actualDelta < 0.0d) {
-                    // Something is wrong, the battery usage shouldn't be negative.
-                    Slog.e(TAG, msg);
+        synchronized (mLock) {
+            for (int i = 0, size = buf.size(); i < size; i++) {
+                final int uid = buf.keyAt(i);
+                final int index = mUidBatteryUsage.indexOfKey(uid);
+                final double delta = Math.max(0.0d,
+                        buf.valueAt(i) - mLastUidBatteryUsage.get(uid, 0.0d));
+                final double before;
+                if (index >= 0) {
+                    before = mUidBatteryUsage.valueAt(index);
+                    mUidBatteryUsage.setValueAt(index, before + delta);
                 } else {
-                    Slog.i(TAG, msg);
+                    before = 0.0d;
+                    mUidBatteryUsage.put(uid, delta);
+                }
+                if (DEBUG_BACKGROUND_BATTERY_TRACKER) {
+                    final double actualDelta = buf.valueAt(i) - mLastUidBatteryUsage.get(uid, 0.0d);
+                    String msg = "Updating mUidBatteryUsage uid=" + uid + ", before=" + before
+                            + ", after=" + mUidBatteryUsage.get(uid, 0.0d)
+                            + ", delta=" + actualDelta
+                            + ", last=" + mLastUidBatteryUsage.get(uid, 0.0d)
+                            + ", curStart=" + curStart
+                            + ", lastLastStart=" + lastUidBatteryUsageStartTs
+                            + ", thisLastStart=" + mLastUidBatteryUsageStartTs;
+                    if (actualDelta < 0.0d) {
+                        // Something is wrong, the battery usage shouldn't be negative.
+                        Slog.e(TAG, msg);
+                    } else {
+                        Slog.i(TAG, msg);
+                    }
                 }
             }
+            // Now update the mLastUidBatteryUsage with the data we just saved above.
+            copyUidBatteryUsage(mTmpUidBatteryUsage2, mLastUidBatteryUsage);
         }
-        // Now update the mLastUidBatteryUsage with the data we just saved above.
-        copyUidBatteryUsage(mTmpUidBatteryUsage2, mLastUidBatteryUsage);
         mTmpUidBatteryUsage2.clear();
 
         if (needUpdateUidBatteryUsageInWindow) {
@@ -473,7 +491,9 @@
                     .includeProcessStateData()
                     .aggregateSnapshots(now - windowSize, lastUidBatteryUsageStartTs);
             updateBatteryUsageStatsOnceInternal(buf, builder, userIds, batteryStatsInternal);
-            copyUidBatteryUsage(buf, mUidBatteryUsageInWindow);
+            synchronized (mLock) {
+                copyUidBatteryUsage(buf, mUidBatteryUsageInWindow);
+            }
         }
     }
 
@@ -584,38 +604,40 @@
         pw.print(prefix);
         pw.println("APP BATTERY STATE TRACKER:");
         updateBatteryUsageStatsIfNecessary(mInjector.currentTimeMillis(), true);
-        final SparseDoubleArray uidConsumers = mUidBatteryUsageInWindow;
-        pw.print("  " + prefix);
-        pw.print("Boot=");
-        TimeUtils.dumpTime(pw, mBootTimestamp);
-        pw.print("  Last battery usage start=");
-        TimeUtils.dumpTime(pw, mLastUidBatteryUsageStartTs);
-        pw.println();
-        pw.print("  " + prefix);
-        pw.print("Battery usage over last ");
-        final String newPrefix = "    " + prefix;
-        final AppBatteryPolicy bgPolicy = mInjector.getPolicy();
-        final long now = SystemClock.elapsedRealtime();
-        final long since = Math.max(0, now - bgPolicy.mBgCurrentDrainWindowMs);
-        pw.println(TimeUtils.formatDuration(now - since));
-        if (uidConsumers.size() == 0) {
-            pw.print(newPrefix);
-            pw.println("(none)");
-        } else {
-            for (int i = 0, size = uidConsumers.size(); i < size; i++) {
-                final int uid = uidConsumers.keyAt(i);
-                final double bgUsage = uidConsumers.valueAt(i);
-                final double exemptedUsage = mAppRestrictionController
-                        .getUidBatteryExemptedUsageSince(uid, since, now);
-                final double reportedUsage = Math.max(0.0d, bgUsage - exemptedUsage);
-                pw.format("%s%s: [%s] %.3f mAh (%4.2f%%) | %.3f mAh (%4.2f%%) | "
-                        + "%.3f mAh (%4.2f%%) | %.3f mAh\n",
-                        newPrefix, UserHandle.formatUid(uid),
-                        PowerExemptionManager.reasonCodeToString(bgPolicy.shouldExemptUid(uid)),
-                        bgUsage , bgPolicy.getPercentage(uid, bgUsage),
-                        exemptedUsage, bgPolicy.getPercentage(-1, exemptedUsage),
-                        reportedUsage, bgPolicy.getPercentage(-1, reportedUsage),
-                        mUidBatteryUsage.get(uid, 0.0d));
+        synchronized (mLock) {
+            final SparseDoubleArray uidConsumers = mUidBatteryUsageInWindow;
+            pw.print("  " + prefix);
+            pw.print("Boot=");
+            TimeUtils.dumpTime(pw, mBootTimestamp);
+            pw.print("  Last battery usage start=");
+            TimeUtils.dumpTime(pw, mLastUidBatteryUsageStartTs);
+            pw.println();
+            pw.print("  " + prefix);
+            pw.print("Battery usage over last ");
+            final String newPrefix = "    " + prefix;
+            final AppBatteryPolicy bgPolicy = mInjector.getPolicy();
+            final long now = SystemClock.elapsedRealtime();
+            final long since = Math.max(0, now - bgPolicy.mBgCurrentDrainWindowMs);
+            pw.println(TimeUtils.formatDuration(now - since));
+            if (uidConsumers.size() == 0) {
+                pw.print(newPrefix);
+                pw.println("(none)");
+            } else {
+                for (int i = 0, size = uidConsumers.size(); i < size; i++) {
+                    final int uid = uidConsumers.keyAt(i);
+                    final double bgUsage = uidConsumers.valueAt(i);
+                    final double exemptedUsage = mAppRestrictionController
+                            .getUidBatteryExemptedUsageSince(uid, since, now);
+                    final double reportedUsage = Math.max(0.0d, bgUsage - exemptedUsage);
+                    pw.format("%s%s: [%s] %.3f mAh (%4.2f%%) | %.3f mAh (%4.2f%%) | "
+                            + "%.3f mAh (%4.2f%%) | %.3f mAh\n",
+                            newPrefix, UserHandle.formatUid(uid),
+                            PowerExemptionManager.reasonCodeToString(bgPolicy.shouldExemptUid(uid)),
+                            bgUsage , bgPolicy.getPercentage(uid, bgUsage),
+                            exemptedUsage, bgPolicy.getPercentage(-1, exemptedUsage),
+                            reportedUsage, bgPolicy.getPercentage(-1, reportedUsage),
+                            mUidBatteryUsage.get(uid, 0.0d));
+                }
             }
         }
         super.dump(pw, prefix);
diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java
index bd63a24..1315293 100644
--- a/services/core/java/com/android/server/am/AppRestrictionController.java
+++ b/services/core/java/com/android/server/am/AppRestrictionController.java
@@ -1314,7 +1314,7 @@
         void onSystemReady() {
             mContext.registerReceiverForAllUsers(mActionButtonReceiver,
                     new IntentFilter(ACTION_FGS_MANAGER_TRAMPOLINE),
-                    MANAGE_ACTIVITY_TASKS, mBgController.mBgHandler);
+                    MANAGE_ACTIVITY_TASKS, mBgController.mBgHandler, Context.RECEIVER_NOT_EXPORTED);
         }
 
         void postRequestBgRestrictedIfNecessary(String packageName, int uid) {
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index c8ad0e8..5da461d 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -1365,7 +1365,7 @@
     }
 
     public void notePhoneDataConnectionState(final int dataType, final boolean hasData,
-            final int serviceType) {
+            final int serviceType, final int nrFrequency) {
         enforceCallingPermission();
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1373,7 +1373,7 @@
             mHandler.post(() -> {
                 synchronized (mStats) {
                     mStats.notePhoneDataConnectionStateLocked(dataType, hasData, serviceType,
-                            elapsedRealtime, uptime);
+                            nrFrequency, elapsedRealtime, uptime);
                 }
             });
         }
@@ -1962,6 +1962,32 @@
         }
     }
 
+    /**
+     * Bluetooth on stat logging
+     */
+    public void noteBluetoothOn(int uid, int reason, String packageName) {
+        if (Binder.getCallingPid() != Process.myPid()) {
+            mContext.enforcePermission(android.Manifest.permission.BLUETOOTH_CONNECT,
+                    Binder.getCallingPid(), uid, null);
+        }
+        FrameworkStatsLog.write_non_chained(FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED,
+                uid, null, FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED__STATE__ENABLED,
+                reason, packageName);
+    }
+
+    /**
+     * Bluetooth off stat logging
+     */
+    public void noteBluetoothOff(int uid, int reason, String packageName) {
+        if (Binder.getCallingPid() != Process.myPid()) {
+            mContext.enforcePermission(android.Manifest.permission.BLUETOOTH_CONNECT,
+                    Binder.getCallingPid(), uid, null);
+        }
+        FrameworkStatsLog.write_non_chained(FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED,
+                uid, null, FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED__STATE__DISABLED,
+                reason, packageName);
+    }
+
     @Override
     public void noteBleScanStarted(final WorkSource ws, final boolean isUnoptimized) {
         enforceCallingPermission();
diff --git a/services/core/java/com/android/server/am/DataConnectionStats.java b/services/core/java/com/android/server/am/DataConnectionStats.java
index 6e39a4c..f0910dc 100644
--- a/services/core/java/com/android/server/am/DataConnectionStats.java
+++ b/services/core/java/com/android/server/am/DataConnectionStats.java
@@ -109,7 +109,7 @@
         }
         try {
             mBatteryStats.notePhoneDataConnectionState(networkType, visible,
-                    mServiceState.getState());
+                    mServiceState.getState(), mServiceState.getNrFrequencyRange());
         } catch (RemoteException e) {
             Log.w(TAG, "Error noting data connection state", e);
         }
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index f1429a5..3c9d29d 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -85,6 +85,7 @@
 import com.android.server.SystemService.TargetUser;
 
 import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.List;
 
 /**
@@ -159,6 +160,29 @@
         new GameManagerShellCommand().exec(this, in, out, err, args, callback, result);
     }
 
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+                != PackageManager.PERMISSION_GRANTED) {
+            writer.println("Permission Denial: can't dump GameManagerService from from pid="
+                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+                    + " without permission " + android.Manifest.permission.DUMP);
+            return;
+        }
+        if (args == null || args.length == 0) {
+            writer.println("*Dump GameManagerService*");
+            dumpAllGameConfigs(writer);
+        }
+    }
+
+    private void dumpAllGameConfigs(PrintWriter pw) {
+        final int userId = ActivityManager.getCurrentUser();
+        String[] packageList = getInstalledGamePackageNames(userId);
+        for (final String packageName : packageList) {
+            pw.println(getInterventionList(packageName));
+        }
+    }
+
     class SettingsHandler extends Handler {
 
         SettingsHandler(Looper looper) {
@@ -1266,8 +1290,7 @@
                     .append(packageName);
             return listStrSb.toString();
         }
-        listStrSb.append("\nPackage name: ")
-                .append(packageName)
+        listStrSb.append("\n")
                 .append(packageConfig.toString());
         return listStrSb.toString();
     }
diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
index 9d4d1c1..366718c 100644
--- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java
+++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
@@ -38,6 +38,7 @@
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageStatsManagerInternal;
 import android.app.usage.UsageStatsManagerInternal.UsageEventListener;
+import android.apphibernation.HibernationStats;
 import android.apphibernation.IAppHibernationService;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -221,7 +222,7 @@
         }
         getContext().enforceCallingOrSelfPermission(
                 android.Manifest.permission.MANAGE_APP_HIBERNATION,
-                "Caller does not have MANAGE_APP_HIBERNATION permission.");
+                "Caller did not have permission while calling " + methodName);
         userId = handleIncomingUser(userId, methodName);
         synchronized (mLock) {
             if (!checkUserStatesExist(userId, methodName)) {
@@ -380,6 +381,46 @@
     }
 
     /**
+     * Return the stats from app hibernation for each package provided.
+     *
+     * @param packageNames the set of packages to return stats for. Returns all if null
+     * @return map from package to stats for that package
+     */
+    public Map<String, HibernationStats> getHibernationStatsForUser(
+            @Nullable Set<String> packageNames, int userId) {
+        Map<String, HibernationStats> statsMap = new ArrayMap<>();
+        String methodName = "getHibernationStatsForUser";
+        getContext().enforceCallingOrSelfPermission(
+                android.Manifest.permission.MANAGE_APP_HIBERNATION,
+                "Caller does not have MANAGE_APP_HIBERNATION permission.");
+        userId = handleIncomingUser(userId, methodName);
+        synchronized (mLock) {
+            if (!checkUserStatesExist(userId, methodName)) {
+                return statsMap;
+            }
+            final Map<String, UserLevelState> userPackageStates = mUserStates.get(userId);
+            Set<String> pkgs = packageNames != null ? packageNames : userPackageStates.keySet();
+            for (String pkgName : pkgs) {
+                if (!mPackageManagerInternal.canQueryPackage(Binder.getCallingUid(), pkgName)) {
+                    // Package not visible to caller
+                    continue;
+                }
+                if (!mGlobalHibernationStates.containsKey(pkgName)
+                        || !userPackageStates.containsKey(pkgName)) {
+                    Slog.w(TAG, String.format(
+                            "No hibernation state associated with package %s user %d. Maybe"
+                                    + "the package was uninstalled? ", pkgName, userId));
+                    continue;
+                }
+                HibernationStats stats = new HibernationStats(
+                        mGlobalHibernationStates.get(pkgName).savedByte);
+                statsMap.put(pkgName, stats);
+            }
+        }
+        return statsMap;
+    }
+
+    /**
      * Put an app into hibernation for a given user, allowing user-level optimizations to occur. Do
      * not hold {@link #mLock} while calling this to avoid deadlock scenarios.
      */
@@ -788,6 +829,13 @@
         }
 
         @Override
+        public Map<String, HibernationStats> getHibernationStatsForUser(
+                @Nullable List<String> packageNames, int userId) {
+            Set<String> pkgsSet = packageNames != null ? new ArraySet<>(packageNames) : null;
+            return mService.getHibernationStatsForUser(pkgsSet, userId);
+        }
+
+        @Override
         public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
                 @Nullable FileDescriptor err, @NonNull String[] args,
                 @Nullable ShellCallback callback, @NonNull ResultReceiver resultReceiver) {
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 42fca9b..47f31d5 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -486,9 +486,7 @@
             return;
         }
         final BluetoothDevice btDevice = deviceList.get(0);
-        final @BluetoothProfile.BtProfileState int state =
-                proxy.getConnectionState(btDevice);
-        if (state == BluetoothProfile.STATE_CONNECTED) {
+        if (proxy.getConnectionState(btDevice) == BluetoothProfile.STATE_CONNECTED) {
             mDeviceBroker.queueOnBluetoothActiveDeviceChanged(
                     new AudioDeviceBroker.BtDeviceChangedData(btDevice, null,
                         new BtProfileConnectionInfo(profile), "mBluetoothProfileServiceListener"));
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContext.java b/services/core/java/com/android/server/biometrics/log/BiometricContext.java
new file mode 100644
index 0000000..8d28298
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/log/BiometricContext.java
@@ -0,0 +1,47 @@
+/*
+ * 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.biometrics.log;
+
+import android.annotation.NonNull;
+import android.hardware.biometrics.common.OperationContext;
+
+import java.util.function.Consumer;
+
+/**
+ * Cache for system state not directly related to biometric operations that is used for
+ * logging or optimizations.
+ */
+public interface BiometricContext {
+    /** Gets the context source. */
+    static BiometricContext getInstance() {
+        return BiometricContextProvider.sInstance.get();
+    }
+
+    /** If the display is in AOD. */
+    boolean isAoD();
+
+    /**
+     * Subscribe to context changes.
+     *
+     * @param context context that will be modified when changed
+     * @param consumer callback when the context is modified
+     */
+    void subscribe(@NonNull OperationContext context, @NonNull Consumer<OperationContext> consumer);
+
+    /** Unsubscribe from context changes. */
+    void unsubscribe(@NonNull OperationContext context);
+}
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
new file mode 100644
index 0000000..65e9e3d
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
@@ -0,0 +1,105 @@
+/*
+ * 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.biometrics.log;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.IBiometricContextListener;
+import android.hardware.biometrics.common.OperationContext;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Singleton;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.statusbar.IStatusBarService;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+
+/**
+ * A default provider for {@link BiometricContext}.
+ */
+class BiometricContextProvider implements BiometricContext {
+
+    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(
+                                    Context.STATUS_BAR_SERVICE)), null /* handler */);
+                }
+            };
+
+    @NonNull
+    private final Map<OperationContext, Consumer<OperationContext>> mSubscribers =
+            new ConcurrentHashMap<>();
+
+    @VisibleForTesting
+    BiometricContextProvider(@NonNull IStatusBarService service, @Nullable Handler handler) {
+        try {
+            service.setBiometicContextListener(new IBiometricContextListener.Stub() {
+                @Override
+                public void onDozeChanged(boolean isDozing) {
+                    mIsDozing = isDozing;
+                    notifyChanged();
+                }
+
+                private void notifyChanged() {
+                    if (handler != null) {
+                        handler.post(() -> notifySubscribers());
+                    } else {
+                        notifySubscribers();
+                    }
+                }
+            });
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Unable to register biometric context listener", e);
+        }
+    }
+
+    private boolean mIsDozing = false;
+
+    @Override
+    public boolean isAoD() {
+        return mIsDozing;
+    }
+
+    @Override
+    public void subscribe(@NonNull OperationContext context,
+            @NonNull Consumer<OperationContext> consumer) {
+        mSubscribers.put(context, consumer);
+    }
+
+    @Override
+    public void unsubscribe(@NonNull OperationContext context) {
+        mSubscribers.remove(context);
+    }
+
+    private void notifySubscribers() {
+        mSubscribers.forEach((context, consumer) -> {
+            context.isAoD = mIsDozing;
+            consumer.accept(context);
+        });
+    }
+}
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 d029af3..0188390 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
@@ -79,6 +79,12 @@
         }
     };
 
+    /** Get a new logger with all unknown fields (for operations that do not require logs). */
+    public static BiometricLogger ofUnknown(@NonNull Context context) {
+        return new BiometricLogger(context, BiometricsProtoEnums.MODALITY_UNKNOWN,
+                BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+    }
+
     /**
      * @param context system_server context
      * @param statsModality One of {@link BiometricsProtoEnums} MODALITY_* constants.
@@ -103,6 +109,11 @@
         mSensorManager = sensorManager;
     }
 
+    /** Creates a new logger with the action replaced with the new action. */
+    public BiometricLogger swapAction(@NonNull Context context, int statsAction) {
+        return new BiometricLogger(context, mStatsModality, statsAction, mStatsClient);
+    }
+
     /** Disable logging metrics and only log critical events, such as system health issues. */
     public void disableMetrics() {
         mShouldLogMetrics = false;
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 8b8103e..e07a68c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
@@ -29,6 +29,9 @@
 import android.os.Vibrator;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+
 import java.util.function.Supplier;
 
 /**
@@ -57,9 +60,9 @@
     public AcquisitionClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull String owner, int cookie, int sensorId, boolean shouldVibrate,
-            int statsModality, int statsAction, int statsClient) {
-        super(context, lazyDaemon, token, listener, userId, owner, cookie, sensorId, statsModality,
-                statsAction, statsClient);
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
+        super(context, lazyDaemon, token, listener, userId, owner, cookie, sensorId,
+                logger, biometricContext);
         mPowerManager = context.getSystemService(PowerManager.class);
         mShouldVibrate = shouldVibrate;
     }
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 b715faf..949edd0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -29,7 +29,6 @@
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricOverlayConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -39,6 +38,8 @@
 
 import com.android.server.biometrics.BiometricsProto;
 import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -93,13 +94,13 @@
     public AuthenticationClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
             int targetUserId, long operationId, boolean restricted, @NonNull String owner,
-            int cookie, boolean requireConfirmation, int sensorId, boolean isStrongBiometric,
-            int statsModality, int statsClient, @Nullable TaskStackListener taskStackListener,
+            int cookie, boolean requireConfirmation, int sensorId,
+            @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
+            boolean isStrongBiometric, @Nullable TaskStackListener taskStackListener,
             @NonNull LockoutTracker lockoutTracker, boolean allowBackgroundAuthentication,
             boolean shouldVibrate, boolean isKeyguardBypassEnabled) {
         super(context, lazyDaemon, token, listener, targetUserId, owner, cookie, sensorId,
-                shouldVibrate, statsModality, BiometricsProtoEnums.ACTION_AUTHENTICATE,
-                statsClient);
+                shouldVibrate, biometricLogger, biometricContext);
         mIsStrongBiometric = isStrongBiometric;
         mOperationId = operationId;
         mRequireConfirmation = requireConfirmation;
diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
index e1f7e2a..1b2e606 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
@@ -21,12 +21,12 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 
 import java.util.NoSuchElementException;
@@ -50,6 +50,7 @@
     @NonNull private final String mOwner;
     private final int mSensorId; // sensorId as configured by the framework
     @NonNull private final BiometricLogger mLogger;
+    @NonNull private final BiometricContext mBiometricContext;
 
     @Nullable private IBinder mToken;
     private long mRequestId;
@@ -82,22 +83,13 @@
      * @param owner      name of the client that owns this
      * @param cookie     BiometricPrompt authentication cookie (to be moved into a subclass soon)
      * @param sensorId   ID of the sensor that the operation should be requested of
-     * @param statsModality One of {@link BiometricsProtoEnums} MODALITY_* constants
-     * @param statsAction   One of {@link BiometricsProtoEnums} ACTION_* constants
-     * @param statsClient   One of {@link BiometricsProtoEnums} CLIENT_* constants
+     * @param logger     framework stats logger
+     * @param biometricContext system context metadata
      */
     public BaseClientMonitor(@NonNull Context context,
             @Nullable IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId,
-            @NonNull String owner, int cookie, int sensorId, int statsModality, int statsAction,
-            int statsClient) {
-        this(context, token, listener, userId, owner, cookie, sensorId,
-                new BiometricLogger(context, statsModality, statsAction, statsClient));
-    }
-
-    @VisibleForTesting
-    BaseClientMonitor(@NonNull Context context,
-            @Nullable IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId,
-            @NonNull String owner, int cookie, int sensorId, @NonNull BiometricLogger logger) {
+            @NonNull String owner, int cookie, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
         mSequentialId = sCount++;
         mContext = context;
         mToken = token;
@@ -108,6 +100,7 @@
         mCookie = cookie;
         mSensorId = sensorId;
         mLogger = logger;
+        mBiometricContext = biometricContext;
 
         try {
             if (token != null) {
@@ -207,20 +200,29 @@
         return false;
     }
 
+    /** System context that may change during operations. */
+    @NonNull
+    protected BiometricContext getBiometricContext() {
+        return mBiometricContext;
+    }
+
     /** Logger for this client */
     @NonNull
     public BiometricLogger getLogger() {
         return mLogger;
     }
 
+    @NonNull
     public final Context getContext() {
         return mContext;
     }
 
+    @NonNull
     public final String getOwnerString() {
         return mOwner;
     }
 
+    @Nullable
     public final ClientMonitorCallbackConverter getListener() {
         return mListener;
     }
@@ -229,6 +231,7 @@
         return mTargetUserId;
     }
 
+    @Nullable
     public final IBinder getToken() {
         return mToken;
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
index 74f4931..483ce75 100644
--- a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
@@ -20,13 +20,14 @@
 import android.content.Context;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricOverlayConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.fingerprint.FingerprintManager;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 
 import java.util.Arrays;
 import java.util.function.Supplier;
@@ -53,10 +54,10 @@
     public EnrollClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull byte[] hardwareAuthToken, @NonNull String owner, @NonNull BiometricUtils utils,
-            int timeoutSec, int statsModality, int sensorId, boolean shouldVibrate) {
+            int timeoutSec, int sensorId, boolean shouldVibrate,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
         super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
-                shouldVibrate, statsModality, BiometricsProtoEnums.ACTION_ENROLL,
-                BiometricsProtoEnums.CLIENT_UNKNOWN);
+                shouldVibrate, logger, biometricContext);
         mBiometricUtils = utils;
         mHardwareAuthToken = Arrays.copyOf(hardwareAuthToken, hardwareAuthToken.length);
         mTimeoutSec = timeoutSec;
diff --git a/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java
index 9689418..2adf0cb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java
@@ -18,12 +18,13 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 
 import java.util.function.Supplier;
 
@@ -33,10 +34,10 @@
 
     public GenerateChallengeClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
-            int userId, @NonNull String owner, int sensorId) {
+            int userId, @NonNull String owner, int sensorId,
+            @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext) {
         super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
-                BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN,
-                BiometricsProtoEnums.CLIENT_UNKNOWN);
+                biometricLogger, biometricContext);
     }
 
     @Override
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 66a1c6e..eabafce 100644
--- a/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java
@@ -19,9 +19,12 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.common.OperationContext;
 import android.os.IBinder;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+
 import java.util.function.Supplier;
 
 /**
@@ -33,6 +36,9 @@
     @NonNull
     protected final Supplier<T> mLazyDaemon;
 
+    @NonNull
+    protected final OperationContext mOperationContext = new OperationContext();
+
     /**
      * @param context    system_server context
      * @param lazyDaemon pointer for lazy retrieval of the HAL
@@ -42,16 +48,15 @@
      * @param owner      name of the client that owns this
      * @param cookie     BiometricPrompt authentication cookie (to be moved into a subclass soon)
      * @param sensorId   ID of the sensor that the operation should be requested of
-     * @param statsModality One of {@link BiometricsProtoEnums} MODALITY_* constants
-     * @param statsAction   One of {@link BiometricsProtoEnums} ACTION_* constants
-     * @param statsClient   One of {@link BiometricsProtoEnums} CLIENT_* constants
+     * @param biometricLogger framework stats logger
+     * @param biometricContext system context metadata
      */
     public HalClientMonitor(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
             @Nullable IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId,
-            @NonNull String owner, int cookie, int sensorId, int statsModality, int statsAction,
-            int statsClient) {
-        super(context, token, listener, userId, owner, cookie, sensorId, statsModality,
-                statsAction, statsClient);
+            @NonNull String owner, int cookie, int sensorId,
+            @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext) {
+        super(context, token, listener, userId, owner, cookie, sensorId,
+                biometricLogger, biometricContext);
         mLazyDaemon = lazyDaemon;
     }
 
@@ -71,4 +76,12 @@
      * {@link #start(ClientMonitorCallback)}.
      */
     public abstract void unableToStart();
+
+    @Override
+    public void destroy() {
+        super.destroy();
+
+        // subclasses should do this earlier in most cases, but ensure it happens now
+        getBiometricContext().unsubscribe(mOperationContext);
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
index 0e6d11e..57ea812 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
@@ -19,11 +19,12 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.os.IBinder;
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -101,19 +102,22 @@
 
     protected abstract InternalEnumerateClient<T> getEnumerateClient(Context context,
             Supplier<T> lazyDaemon, IBinder token, int userId, String owner,
-            List<S> enrolledList, BiometricUtils<S> utils, int sensorId);
+            List<S> enrolledList, BiometricUtils<S> utils, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext);
 
     protected abstract RemovalClient<S, T> getRemovalClient(Context context,
             Supplier<T> lazyDaemon, IBinder token, int biometricId, int userId, String owner,
-            BiometricUtils<S> utils, int sensorId, Map<Integer, Long> authenticatorIds);
+            BiometricUtils<S> utils, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            Map<Integer, Long> authenticatorIds);
 
     protected InternalCleanupClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
-            int userId, @NonNull String owner, int sensorId, int statsModality,
+            int userId, @NonNull String owner, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             @NonNull List<S> enrolledList, @NonNull BiometricUtils<S> utils,
             @NonNull Map<Integer, Long> authenticatorIds) {
         super(context, lazyDaemon, null /* token */, null /* ClientMonitorCallbackConverter */,
-                userId, owner, 0 /* cookie */, sensorId, statsModality,
-                BiometricsProtoEnums.ACTION_ENUMERATE, BiometricsProtoEnums.CLIENT_UNKNOWN);
+                userId, owner, 0 /* cookie */, sensorId, logger, biometricContext);
         mBiometricUtils = utils;
         mAuthenticatorIds = authenticatorIds;
         mEnrolledList = enrolledList;
@@ -127,7 +131,8 @@
         mUnknownHALTemplates.remove(template);
         mCurrentTask = getRemovalClient(getContext(), mLazyDaemon, getToken(),
                 template.mIdentifier.getBiometricId(), template.mUserId,
-                getContext().getPackageName(), mBiometricUtils, getSensorId(), mAuthenticatorIds);
+                getContext().getPackageName(), mBiometricUtils, getSensorId(),
+                getLogger(), getBiometricContext(), mAuthenticatorIds);
 
         getLogger().logUnknownEnrollmentInHal();
 
@@ -145,7 +150,8 @@
 
         // Start enumeration. Removal will start if necessary, when enumeration is completed.
         mCurrentTask = getEnumerateClient(getContext(), mLazyDaemon, getToken(), getTargetUserId(),
-                getOwnerString(), mEnrolledList, mBiometricUtils, getSensorId());
+                getOwnerString(), mEnrolledList, mBiometricUtils, getSensorId(), getLogger(),
+                getBiometricContext());
 
         Slog.d(TAG, "Starting enumerate: " + mCurrentTask);
         mCurrentTask.start(mEnumerateCallback);
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
index 5f97f37..7f8f38f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
@@ -19,11 +19,12 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.os.IBinder;
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -47,12 +48,14 @@
     protected InternalEnumerateClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
             @NonNull IBinder token, int userId, @NonNull String owner,
             @NonNull List<? extends BiometricAuthenticator.Identifier> enrolledList,
-            @NonNull BiometricUtils utils, int sensorId, int statsModality) {
+            @NonNull BiometricUtils utils, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
         // Internal enumerate does not need to send results to anyone. Cleanup (enumerate + remove)
         // is all done internally.
         super(context, lazyDaemon, token, null /* ClientMonitorCallbackConverter */, userId, owner,
-                0 /* cookie */, sensorId, statsModality, BiometricsProtoEnums.ACTION_ENUMERATE,
-                BiometricsProtoEnums.CLIENT_UNKNOWN);
+                0 /* cookie */, sensorId, logger, biometricContext);
+        //, BiometricsProtoEnums.ACTION_ENUMERATE,
+          //      BiometricsProtoEnums.CLIENT_UNKNOWN);
         mEnrolledList = enrolledList;
         mUtils = utils;
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java b/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java
index 697d77c..d5aa5e2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java
@@ -19,12 +19,13 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.os.RemoteException;
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 
 import java.util.Map;
 import java.util.function.Supplier;
@@ -42,12 +43,13 @@
     @NonNull private final IInvalidationCallback mInvalidationCallback;
 
     public InvalidationClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
-            int userId, int sensorId, @NonNull Map<Integer, Long> authenticatorIds,
+            int userId, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            @NonNull Map<Integer, Long> authenticatorIds,
             @NonNull IInvalidationCallback callback) {
         super(context, lazyDaemon, null /* token */, null /* listener */, userId,
                 context.getOpPackageName(), 0 /* cookie */, sensorId,
-                BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN,
-                BiometricsProtoEnums.CLIENT_UNKNOWN);
+                logger, biometricContext);
         mAuthenticatorIds = authenticatorIds;
         mInvalidationCallback = callback;
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java b/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java
index b2661a2..1097bb7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java
@@ -20,10 +20,11 @@
 import android.content.Context;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricManager;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.IInvalidationCallback;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 
 /**
  * ClientMonitor subclass responsible for coordination of authenticatorId invalidation of other
@@ -74,11 +75,10 @@
     };
 
     public InvalidationRequesterClient(@NonNull Context context, int userId, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             @NonNull BiometricUtils<S> utils) {
         super(context, null /* token */, null /* listener */, userId,
-                context.getOpPackageName(), 0 /* cookie */, sensorId,
-                BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN,
-                BiometricsProtoEnums.CLIENT_UNKNOWN);
+                context.getOpPackageName(), 0 /* cookie */, sensorId, logger, biometricContext);
         mBiometricManager = context.getSystemService(BiometricManager.class);
         mUtils = utils;
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
index a0cef94..07ce841 100644
--- a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
@@ -19,12 +19,13 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 
 import java.util.Map;
 import java.util.function.Supplier;
@@ -44,10 +45,12 @@
     public RemovalClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
             int userId, @NonNull String owner, @NonNull BiometricUtils<S> utils, int sensorId,
-            @NonNull Map<Integer, Long> authenticatorIds, int statsModality) {
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            @NonNull Map<Integer, Long> authenticatorIds) {
         super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
-                statsModality, BiometricsProtoEnums.ACTION_REMOVE,
-                BiometricsProtoEnums.CLIENT_UNKNOWN);
+                logger, biometricContext);
+        //, BiometricsProtoEnums.ACTION_REMOVE,
+          //      BiometricsProtoEnums.CLIENT_UNKNOWN);
         mBiometricUtils = utils;
         mAuthenticatorIds = authenticatorIds;
         mHasEnrollmentsBeforeStarting = !utils.getBiometricsForUser(context, userId).isEmpty();
diff --git a/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java
index 7d83863..88f4da2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java
@@ -18,20 +18,21 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.os.IBinder;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 
 import java.util.function.Supplier;
 
 public abstract class RevokeChallengeClient<T> extends HalClientMonitor<T> {
 
     public RevokeChallengeClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
-            @NonNull IBinder token, int userId, @NonNull String owner, int sensorId) {
+            @NonNull IBinder token, int userId, @NonNull String owner, int sensorId,
+            @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext) {
         super(context, lazyDaemon, token, null /* listener */, userId, owner,
-                0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
-                BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+                0 /* cookie */, sensorId, biometricLogger, biometricContext);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java
index 1bc3248..21c9f64 100644
--- a/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java
@@ -19,11 +19,12 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.os.IBinder;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 
 import java.util.function.Supplier;
 
@@ -47,10 +48,10 @@
 
     public StartUserClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
             @Nullable IBinder token, int userId, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             @NonNull UserStartedCallback<U> callback) {
         super(context, lazyDaemon, token, null /* listener */, userId, context.getOpPackageName(),
-                0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
-                BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+                0 /* cookie */, sensorId, logger, biometricContext);
         mUserStartedCallback = callback;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
index 3eafbb8..e8654dc 100644
--- a/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
@@ -19,11 +19,12 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.os.IBinder;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 
 import java.util.function.Supplier;
 
@@ -47,10 +48,10 @@
 
     public StopUserClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
             @Nullable IBinder token, int userId, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             @NonNull UserStoppedCallback callback) {
         super(context, lazyDaemon, token, null /* listener */, userId, context.getOpPackageName(),
-                0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
-                BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+                0 /* cookie */, sensorId, logger, biometricContext);
         mUserStoppedCallback = callback;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java
index 006667a..29eee6b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java
@@ -16,11 +16,11 @@
 
 package com.android.server.biometrics.sensors.face.aidl;
 
+import static com.android.server.biometrics.sensors.face.aidl.Sensor.HalSessionCallback;
+
 import android.annotation.NonNull;
 import android.hardware.biometrics.face.ISession;
 
-import static com.android.server.biometrics.sensors.face.aidl.Sensor.HalSessionCallback;
-
 /**
  * A holder for an AIDL {@link ISession} with additional metadata about the current user
  * and the backend.
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 c4e0502..75a1e0c 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
@@ -25,7 +25,6 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricFaceConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.common.ICancellationSignal;
 import android.hardware.biometrics.common.OperationContext;
 import android.hardware.biometrics.common.OperationReason;
@@ -37,7 +36,10 @@
 import android.util.Slog;
 
 import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AuthenticationClient;
 import com.android.server.biometrics.sensors.BiometricNotificationUtils;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -76,19 +78,36 @@
             @NonNull IBinder token, long requestId,
             @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
             boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId,
-            boolean isStrongBiometric, int statsClient, @NonNull UsageStats usageStats,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            boolean isStrongBiometric, @NonNull UsageStats usageStats,
             @NonNull LockoutCache lockoutCache, boolean allowBackgroundAuthentication,
             boolean isKeyguardBypassEnabled) {
+        this(context, lazyDaemon, token, requestId, listener, targetUserId, operationId,
+                restricted, owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
+                isStrongBiometric, usageStats, lockoutCache, allowBackgroundAuthentication,
+                isKeyguardBypassEnabled, context.getSystemService(SensorPrivacyManager.class));
+    }
+
+    @VisibleForTesting
+    FaceAuthenticationClient(@NonNull Context context,
+            @NonNull Supplier<AidlSession> lazyDaemon,
+            @NonNull IBinder token, long requestId,
+            @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
+            boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            boolean isStrongBiometric, @NonNull UsageStats usageStats,
+            @NonNull LockoutCache lockoutCache, boolean allowBackgroundAuthentication,
+            boolean isKeyguardBypassEnabled, SensorPrivacyManager sensorPrivacyManager) {
         super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
-                owner, cookie, requireConfirmation, sensorId, isStrongBiometric,
-                BiometricsProtoEnums.MODALITY_FACE, statsClient, null /* taskStackListener */,
-                lockoutCache, allowBackgroundAuthentication, true /* shouldVibrate */,
+                owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
+                isStrongBiometric, null /* taskStackListener */, lockoutCache,
+                allowBackgroundAuthentication, true /* shouldVibrate */,
                 isKeyguardBypassEnabled);
         setRequestId(requestId);
         mUsageStats = usageStats;
         mLockoutCache = lockoutCache;
         mNotificationManager = context.getSystemService(NotificationManager.class);
-        mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
+        mSensorPrivacyManager = sensorPrivacyManager;
 
         final Resources resources = getContext().getResources();
         mBiometricPromptIgnoreList = resources.getIntArray(
@@ -139,10 +158,10 @@
 
         if (session.hasContextMethods()) {
             final OperationContext context = new OperationContext();
-            // TODO: add reason, id, and isAoD
+            // TODO: add reason, id
             context.id = 0;
             context.reason = OperationReason.UNKNOWN;
-            context.isAoD = false;
+            context.isAoD = getBiometricContext().isAoD();
             context.isCrypto = isCryptoOperation();
             return session.getSession().authenticateWithContext(mOperationId, context);
         } else {
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 3f3db43..c79e601 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
@@ -21,7 +21,6 @@
 import android.content.Context;
 import android.hardware.SensorPrivacyManager;
 import android.hardware.biometrics.BiometricConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.common.ICancellationSignal;
 import android.hardware.biometrics.common.OperationContext;
 import android.hardware.biometrics.common.OperationReason;
@@ -29,7 +28,10 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AcquisitionClient;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
@@ -52,13 +54,26 @@
     FaceDetectClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token, long requestId,
             @NonNull ClientMonitorCallbackConverter listener, int userId,
-            @NonNull String owner, int sensorId, boolean isStrongBiometric, int statsClient) {
+            @NonNull String owner, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            boolean isStrongBiometric) {
+        this(context, lazyDaemon, token, requestId, listener, userId, owner, sensorId,
+                logger, biometricContext, isStrongBiometric,
+                context.getSystemService(SensorPrivacyManager.class));
+    }
+
+    @VisibleForTesting
+    FaceDetectClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
+            @NonNull IBinder token, long requestId,
+            @NonNull ClientMonitorCallbackConverter listener, int userId,
+            @NonNull String owner, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            boolean isStrongBiometric, SensorPrivacyManager sensorPrivacyManager) {
         super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
-                true /* shouldVibrate */, BiometricsProtoEnums.MODALITY_FACE,
-                BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient);
+                true /* shouldVibrate */, logger, biometricContext);
         setRequestId(requestId);
         mIsStrongBiometric = isStrongBiometric;
-        mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
+        mSensorPrivacyManager = sensorPrivacyManager;
     }
 
     @Override
@@ -102,10 +117,10 @@
 
         if (session.hasContextMethods()) {
             final OperationContext context = new OperationContext();
-            // TODO: add reason, id, and isAoD
+            // TODO: add reason, id
             context.id = 0;
             context.reason = OperationReason.UNKNOWN;
-            context.isAoD = false;
+            context.isAoD = getBiometricContext().isAoD();
             context.isCrypto = isCryptoOperation();
             return session.getSession().detectInteractionWithContext(context);
         } else {
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 8dc53b6..6f6dadc8 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
@@ -20,7 +20,6 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.biometrics.BiometricFaceConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.common.ICancellationSignal;
 import android.hardware.biometrics.common.OperationContext;
 import android.hardware.biometrics.common.OperationReason;
@@ -40,6 +39,8 @@
 import com.android.internal.R;
 import com.android.server.biometrics.HardwareAuthTokenUtils;
 import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricNotificationUtils;
 import com.android.server.biometrics.sensors.BiometricUtils;
@@ -89,11 +90,11 @@
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull byte[] hardwareAuthToken, @NonNull String opPackageName, long requestId,
             @NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec,
-            @Nullable Surface previewSurface, int sensorId, int maxTemplatesPerUser,
-            boolean debugConsent) {
+            @Nullable Surface previewSurface, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            int maxTemplatesPerUser, boolean debugConsent) {
         super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, opPackageName, utils,
-                timeoutSec, BiometricsProtoEnums.MODALITY_FACE, sensorId,
-                false /* shouldVibrate */);
+                timeoutSec, sensorId, false /* shouldVibrate */, logger, biometricContext);
         setRequestId(requestId);
         mEnrollIgnoreList = getContext().getResources()
                 .getIntArray(R.array.config_face_acquire_enroll_ignorelist);
@@ -200,10 +201,10 @@
 
         if (session.hasContextMethods()) {
             final OperationContext context = new OperationContext();
-            // TODO: add reason, id, and isAoD
+            // TODO: add reason, id
             context.id = 0;
             context.reason = OperationReason.UNKNOWN;
-            context.isAoD = false;
+            context.isAoD = getBiometricContext().isAoD();
             context.isCrypto = isCryptoOperation();
             return session.getSession().enrollWithContext(
                     hat, EnrollmentType.DEFAULT, features, mHwPreviewHandle, context);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
index bdad268..165c3a2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
@@ -23,6 +23,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.GenerateChallengeClient;
 
@@ -37,8 +39,10 @@
     FaceGenerateChallengeClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token,
             @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
-            int sensorId) {
-        super(context, lazyDaemon, token, listener, userId, owner, sensorId);
+            int sensorId, @NonNull BiometricLogger logger,
+            @NonNull BiometricContext biometricContext) {
+        super(context, lazyDaemon, token, listener, userId, owner, sensorId, logger,
+                biometricContext);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java
index 2f3187b..1f4f612 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java
@@ -18,11 +18,12 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.os.RemoteException;
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.HalClientMonitor;
 
@@ -38,10 +39,10 @@
     FaceGetAuthenticatorIdClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon,
             int userId, @NonNull String opPackageName, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             Map<Integer, Long> authenticatorIds) {
         super(context, lazyDaemon, null /* token */, null /* listener */, userId, opPackageName,
-                0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_FACE,
-                BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+                0 /* cookie */, sensorId, logger, biometricContext);
         mAuthenticatorIds = authenticatorIds;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
index 79479be..ef3b345 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
@@ -20,7 +20,6 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.biometrics.BiometricFaceConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.face.IFace;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -28,6 +27,8 @@
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.ErrorConsumer;
@@ -48,10 +49,10 @@
 
     FaceGetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId,
-            @NonNull String owner, int sensorId) {
+            @NonNull String owner, int sensorId, @NonNull BiometricLogger logger,
+            @NonNull BiometricContext biometricContext) {
         super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
-                BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN,
-                BiometricsProtoEnums.CLIENT_UNKNOWN);
+                logger, biometricContext);
         mUserId = userId;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
index a2b0339..54f2033 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
@@ -18,11 +18,12 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.face.IFace;
 import android.hardware.face.Face;
 import android.os.IBinder;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.BiometricUtils;
 import com.android.server.biometrics.sensors.InternalCleanupClient;
 import com.android.server.biometrics.sensors.InternalEnumerateClient;
@@ -39,29 +40,32 @@
 
     FaceInternalCleanupClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon, int userId, @NonNull String owner,
-            int sensorId, @NonNull List<Face> enrolledList, @NonNull BiometricUtils<Face> utils,
-            @NonNull Map<Integer, Long> authenticatorIds) {
-        super(context, lazyDaemon, userId, owner, sensorId, BiometricsProtoEnums.MODALITY_FACE,
+            int sensorId, @NonNull BiometricLogger logger,
+            @NonNull BiometricContext biometricContext, @NonNull List<Face> enrolledList,
+            @NonNull BiometricUtils<Face> utils, @NonNull Map<Integer, Long> authenticatorIds) {
+        super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext,
                 enrolledList, utils, authenticatorIds);
     }
 
     @Override
     protected InternalEnumerateClient<AidlSession> getEnumerateClient(Context context,
             Supplier<AidlSession> lazyDaemon, IBinder token, int userId, String owner,
-            List<Face> enrolledList, BiometricUtils<Face> utils, int sensorId) {
+            List<Face> enrolledList, BiometricUtils<Face> utils, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
         return new FaceInternalEnumerateClient(context, lazyDaemon, token, userId, owner,
-                enrolledList, utils, sensorId);
+                enrolledList, utils, sensorId, logger, biometricContext);
     }
 
     @Override
     protected RemovalClient<Face, AidlSession> getRemovalClient(Context context,
             Supplier<AidlSession> lazyDaemon, IBinder token,
             int biometricId, int userId, String owner, BiometricUtils<Face> utils, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             Map<Integer, Long> authenticatorIds) {
         // Internal remove does not need to send results to anyone. Cleanup (enumerate + remove)
         // is all done internally.
         return new FaceRemovalClient(context, lazyDaemon, token,
                 null /* ClientMonitorCallbackConverter */, new int[] {biometricId}, userId, owner,
-                utils, sensorId, authenticatorIds);
+                utils, sensorId, logger, biometricContext, authenticatorIds);
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClient.java
index 88c9d3b..d85455e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClient.java
@@ -18,13 +18,14 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.face.IFace;
 import android.hardware.face.Face;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.BiometricUtils;
 import com.android.server.biometrics.sensors.InternalEnumerateClient;
 
@@ -40,9 +41,10 @@
     FaceInternalEnumerateClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, int userId,
             @NonNull String owner, @NonNull List<Face> enrolledList,
-            @NonNull BiometricUtils<Face> utils, int sensorId) {
+            @NonNull BiometricUtils<Face> utils, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
         super(context, lazyDaemon, token, userId, owner, enrolledList, utils, sensorId,
-                BiometricsProtoEnums.MODALITY_FACE);
+                logger, biometricContext);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInvalidationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInvalidationClient.java
index 04ea2cfc..39d8de0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInvalidationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInvalidationClient.java
@@ -23,6 +23,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.InvalidationClient;
 
 import java.util.Map;
@@ -33,8 +35,10 @@
 
     public FaceInvalidationClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon, int userId, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             @NonNull Map<Integer, Long> authenticatorIds, @NonNull IInvalidationCallback callback) {
-        super(context, lazyDaemon, userId, sensorId, authenticatorIds, callback);
+        super(context, lazyDaemon, userId, sensorId, logger, biometricContext,
+                authenticatorIds, callback);
     }
 
     @Override
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 9d7a552..64b0892 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
@@ -23,6 +23,7 @@
 import android.app.TaskStackListener;
 import android.content.Context;
 import android.content.pm.UserInfo;
+import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.ComponentInfoInternal;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
@@ -47,6 +48,8 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AuthenticationClient;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -237,6 +240,9 @@
             final FaceGetAuthenticatorIdClient client = new FaceGetAuthenticatorIdClient(
                     mContext, mSensors.get(sensorId).getLazySession(), userId,
                     mContext.getOpPackageName(), sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    BiometricContext.getInstance(),
                     mSensors.get(sensorId).getAuthenticatorIds());
 
             scheduleForSensor(sensorId, client);
@@ -247,6 +253,7 @@
         mHandler.post(() -> {
             final InvalidationRequesterClient<Face> client =
                     new InvalidationRequesterClient<>(mContext, userId, sensorId,
+                            BiometricLogger.ofUnknown(mContext), BiometricContext.getInstance(),
                             FaceUtils.getInstance(sensorId));
             scheduleForSensor(sensorId, client);
         });
@@ -285,6 +292,9 @@
         mHandler.post(() -> {
             final FaceInvalidationClient client = new FaceInvalidationClient(mContext,
                     mSensors.get(sensorId).getLazySession(), userId, sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    BiometricContext.getInstance(),
                     mSensors.get(sensorId).getAuthenticatorIds(), callback);
             scheduleForSensor(sensorId, client);
         });
@@ -311,7 +321,10 @@
         mHandler.post(() -> {
             final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
                     mSensors.get(sensorId).getLazySession(), token,
-                    new ClientMonitorCallbackConverter(receiver), userId, opPackageName, sensorId);
+                    new ClientMonitorCallbackConverter(receiver), userId, opPackageName, sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    BiometricContext.getInstance());
             scheduleForSensor(sensorId, client);
         });
     }
@@ -322,7 +335,9 @@
         mHandler.post(() -> {
             final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
                     mSensors.get(sensorId).getLazySession(), token, userId, opPackageName, sensorId,
-                    challenge);
+                    createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    BiometricContext.getInstance(), challenge);
             scheduleForSensor(sensorId, client);
         });
     }
@@ -340,8 +355,10 @@
                     mSensors.get(sensorId).getLazySession(), token,
                     new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
                     opPackageName, id, FaceUtils.getInstance(sensorId), disabledFeatures,
-                    ENROLL_TIMEOUT_SEC, previewSurface, sensorId, maxTemplatesPerUser,
-                    debugConsent);
+                    ENROLL_TIMEOUT_SEC, previewSurface, sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_ENROLL,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    BiometricContext.getInstance(), maxTemplatesPerUser, debugConsent);
             scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
                 @Override
                 public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
@@ -372,8 +389,9 @@
             final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
             final FaceDetectClient client = new FaceDetectClient(mContext,
                     mSensors.get(sensorId).getLazySession(),
-                    token, id, callback, userId, opPackageName,
-                    sensorId, isStrongBiometric, statsClient);
+                    token, id, callback, userId, opPackageName, sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+                    BiometricContext.getInstance(), isStrongBiometric);
             scheduleForSensor(sensorId, client);
         });
 
@@ -396,7 +414,9 @@
             final FaceAuthenticationClient client = new FaceAuthenticationClient(
                     mContext, mSensors.get(sensorId).getLazySession(), token, requestId, callback,
                     userId, operationId, restricted, opPackageName, cookie,
-                    false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
+                    false /* requireConfirmation */, sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+                    BiometricContext.getInstance(), isStrongBiometric,
                     mUsageStats, mSensors.get(sensorId).getLockoutCache(),
                     allowBackgroundAuthentication, isKeyguardBypassEnabled);
             scheduleForSensor(sensorId, client);
@@ -450,6 +470,9 @@
                     mSensors.get(sensorId).getLazySession(), token,
                     new ClientMonitorCallbackConverter(receiver), faceIds, userId,
                     opPackageName, FaceUtils.getInstance(sensorId), sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    BiometricContext.getInstance(),
                     mSensors.get(sensorId).getAuthenticatorIds());
             scheduleForSensor(sensorId, client);
         });
@@ -460,7 +483,10 @@
         mHandler.post(() -> {
             final FaceResetLockoutClient client = new FaceResetLockoutClient(
                     mContext, mSensors.get(sensorId).getLazySession(), userId,
-                    mContext.getOpPackageName(), sensorId, hardwareAuthToken,
+                    mContext.getOpPackageName(), sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    BiometricContext.getInstance(), hardwareAuthToken,
                     mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher);
 
             scheduleForSensor(sensorId, client);
@@ -481,7 +507,9 @@
             final FaceSetFeatureClient client = new FaceSetFeatureClient(mContext,
                     mSensors.get(sensorId).getLazySession(), token,
                     new ClientMonitorCallbackConverter(receiver), userId,
-                    mContext.getOpPackageName(), sensorId, feature, enabled, hardwareAuthToken);
+                    mContext.getOpPackageName(), sensorId,
+                    BiometricLogger.ofUnknown(mContext), BiometricContext.getInstance(),
+                    feature, enabled, hardwareAuthToken);
             scheduleForSensor(sensorId, client);
         });
     }
@@ -498,7 +526,8 @@
             }
             final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext,
                     mSensors.get(sensorId).getLazySession(), token, callback, userId,
-                    mContext.getOpPackageName(), sensorId);
+                    mContext.getOpPackageName(), sensorId, BiometricLogger.ofUnknown(mContext),
+                    BiometricContext.getInstance());
             scheduleForSensor(sensorId, client);
         });
     }
@@ -518,13 +547,21 @@
             final FaceInternalCleanupClient client =
                     new FaceInternalCleanupClient(mContext,
                             mSensors.get(sensorId).getLazySession(), userId,
-                            mContext.getOpPackageName(), sensorId, enrolledList,
+                            mContext.getOpPackageName(), sensorId,
+                            createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
+                                    BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricContext.getInstance(), enrolledList,
                             FaceUtils.getInstance(sensorId),
                             mSensors.get(sensorId).getAuthenticatorIds());
             scheduleForSensor(sensorId, client, callback);
         });
     }
 
+    private BiometricLogger createLogger(int statsAction, int statsClient) {
+        return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FACE,
+                statsAction, statsClient);
+    }
+
     @Override
     public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
             boolean clearSchedulerBuffer) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java
index 130a05a..0512017 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java
@@ -18,13 +18,14 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.face.IFace;
 import android.hardware.face.Face;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.BiometricUtils;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.RemovalClient;
@@ -44,9 +45,10 @@
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
             int[] biometricIds, int userId, @NonNull String owner,
             @NonNull BiometricUtils<Face> utils, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             @NonNull Map<Integer, Long> authenticatorIds) {
         super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId,
-                authenticatorIds, BiometricsProtoEnums.MODALITY_FACE);
+                logger, biometricContext, authenticatorIds);
         mBiometricIds = biometricIds;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
index 67bf3f5..de0a36a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.face.IFace;
 import android.hardware.keymaster.HardwareAuthToken;
 import android.os.RemoteException;
@@ -26,6 +25,8 @@
 
 import com.android.server.biometrics.BiometricsProto;
 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.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ErrorConsumer;
 import com.android.server.biometrics.sensors.HalClientMonitor;
@@ -50,11 +51,11 @@
 
     FaceResetLockoutClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon, int userId, String owner, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             @NonNull byte[] hardwareAuthToken, @NonNull LockoutCache lockoutTracker,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
         super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
-                0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
-                BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+                0 /* cookie */, sensorId, logger, biometricContext);
         mHardwareAuthToken = HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken);
         mLockoutCache = lockoutTracker;
         mLockoutResetDispatcher = lockoutResetDispatcher;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java
index acd2e05..8838345 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java
@@ -23,6 +23,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.RevokeChallengeClient;
 
 import java.util.function.Supplier;
@@ -38,8 +40,10 @@
 
     FaceRevokeChallengeClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token,
-            int userId, @NonNull String owner, int sensorId, long challenge) {
-        super(context, lazyDaemon, token, userId, owner, sensorId);
+            int userId, @NonNull String owner, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            long challenge) {
+        super(context, lazyDaemon, token, userId, owner, sensorId, logger, biometricContext);
         mChallenge = challenge;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java
index 9d535a2..6c14387 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.face.IFace;
 import android.hardware.keymaster.HardwareAuthToken;
 import android.os.IBinder;
@@ -27,6 +26,8 @@
 
 import com.android.server.biometrics.BiometricsProto;
 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.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.ErrorConsumer;
@@ -47,11 +48,11 @@
 
     FaceSetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
-            @NonNull String owner, int sensorId, int feature, boolean enabled,
-            byte[] hardwareAuthToken) {
+            @NonNull String owner, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            int feature, boolean enabled, byte[] hardwareAuthToken) {
         super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
-                BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN,
-                BiometricsProtoEnums.CLIENT_UNKNOWN);
+                logger, biometricContext);
         mFeature = feature;
         mEnabled = enabled;
         mHardwareAuthToken = HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java
index f5a98ff..61e7ab7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java
@@ -27,6 +27,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.StartUserClient;
 
@@ -40,9 +42,10 @@
     public FaceStartUserClient(@NonNull Context context,
             @NonNull Supplier<IFace> lazyDaemon,
             @Nullable IBinder token, int userId, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             @NonNull ISessionCallback sessionCallback,
             @NonNull UserStartedCallback<ISession> callback) {
-        super(context, lazyDaemon, token, userId, sensorId, callback);
+        super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback);
         mSessionCallback = sessionCallback;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java
index 48b4856..0110ae9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java
@@ -23,6 +23,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.StopUserClient;
 
@@ -33,8 +35,9 @@
 
     public FaceStopUserClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
             @Nullable IBinder token, int userId, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             @NonNull UserStoppedCallback callback) {
-        super(context, lazyDaemon, token, userId, sensorId, callback);
+        super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback);
     }
 
     @Override
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 33e6fa4..fa07d12 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
@@ -42,12 +42,15 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.biometrics.HardwareAuthTokenUtils;
 import com.android.server.biometrics.SensorServiceStateProto;
 import com.android.server.biometrics.SensorStateProto;
 import com.android.server.biometrics.UserStateProto;
 import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AuthenticationConsumer;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricScheduler;
@@ -88,7 +91,8 @@
     @NonNull private final Supplier<AidlSession> mLazySession;
     @Nullable private AidlSession mCurrentSession;
 
-    static class HalSessionCallback extends ISessionCallback.Stub {
+    @VisibleForTesting
+    public static class HalSessionCallback extends ISessionCallback.Stub {
         /**
          * Interface to sends results to the HalSessionCallback's owner.
          */
@@ -487,7 +491,9 @@
                     @Override
                     public StopUserClient<?> getStopUserClient(int userId) {
                         return new FaceStopUserClient(mContext, mLazySession, mToken, userId,
-                                mSensorProperties.sensorId, () -> mCurrentSession = null);
+                                mSensorProperties.sensorId,
+                                BiometricLogger.ofUnknown(mContext), BiometricContext.getInstance(),
+                                () -> mCurrentSession = null);
                     }
 
                     @NonNull
@@ -523,6 +529,7 @@
 
                         return new FaceStartUserClient(mContext, provider::getHalInstance,
                                 mToken, newUserId, mSensorProperties.sensorId,
+                                BiometricLogger.ofUnknown(mContext), BiometricContext.getInstance(),
                                 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 586abe2..be1ed7d 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
@@ -55,6 +55,8 @@
 import com.android.server.biometrics.SensorStateProto;
 import com.android.server.biometrics.UserStateProto;
 import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AcquisitionClient;
 import com.android.server.biometrics.sensors.AuthenticationConsumer;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
@@ -533,7 +535,10 @@
 
             final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
                     mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
-                    opPackageName, mSensorId, sSystemClock.millis());
+                    opPackageName, mSensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    BiometricContext.getInstance(), sSystemClock.millis());
             mGeneratedChallengeCache = client;
             mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
                 @Override
@@ -562,7 +567,10 @@
             mGeneratedChallengeCache = null;
 
             final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
-                    mLazyDaemon, token, userId, opPackageName, mSensorId);
+                    mLazyDaemon, token, userId, opPackageName, mSensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    BiometricContext.getInstance());
             mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
                 @Override
                 public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
@@ -590,7 +598,10 @@
             final FaceEnrollClient client = new FaceEnrollClient(mContext, mLazyDaemon, token,
                     new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
                     opPackageName, id, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures,
-                    ENROLL_TIMEOUT_SEC, previewSurface, mSensorId);
+                    ENROLL_TIMEOUT_SEC, previewSurface, mSensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_ENROLL,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    BiometricContext.getInstance());
 
             mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
                 @Override
@@ -637,7 +648,8 @@
             final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext,
                     mLazyDaemon, token, requestId, receiver, userId, operationId, restricted,
                     opPackageName, cookie, false /* requireConfirmation */, mSensorId,
-                    isStrongBiometric, statsClient, mLockoutTracker, mUsageStats,
+                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+                    BiometricContext.getInstance(), isStrongBiometric, mLockoutTracker, mUsageStats,
                     allowBackgroundAuthentication, isKeyguardBypassEnabled);
             mScheduler.scheduleClientMonitor(client);
         });
@@ -670,7 +682,10 @@
 
             final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token,
                     new ClientMonitorCallbackConverter(receiver), faceId, userId, opPackageName,
-                    FaceUtils.getLegacyInstance(mSensorId), mSensorId, mAuthenticatorIds);
+                    FaceUtils.getLegacyInstance(mSensorId), mSensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    BiometricContext.getInstance(), mAuthenticatorIds);
             mScheduler.scheduleClientMonitor(client);
         });
     }
@@ -685,7 +700,10 @@
             final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token,
                     new ClientMonitorCallbackConverter(receiver), 0 /* faceId */, userId,
                     opPackageName,
-                    FaceUtils.getLegacyInstance(mSensorId), mSensorId, mAuthenticatorIds);
+                    FaceUtils.getLegacyInstance(mSensorId), mSensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    BiometricContext.getInstance(), mAuthenticatorIds);
             mScheduler.scheduleClientMonitor(client);
         });
     }
@@ -702,7 +720,9 @@
 
             final FaceResetLockoutClient client = new FaceResetLockoutClient(mContext,
                     mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
-                    hardwareAuthToken);
+                    createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    BiometricContext.getInstance(), hardwareAuthToken);
             mScheduler.scheduleClientMonitor(client);
         });
     }
@@ -723,7 +743,8 @@
             final int faceId = faces.get(0).getBiometricId();
             final FaceSetFeatureClient client = new FaceSetFeatureClient(mContext,
                     mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
-                    opPackageName, mSensorId, feature, enabled, hardwareAuthToken, faceId);
+                    opPackageName, mSensorId, BiometricLogger.ofUnknown(mContext),
+                    BiometricContext.getInstance(), feature, enabled, hardwareAuthToken, faceId);
             mScheduler.scheduleClientMonitor(client);
         });
     }
@@ -742,7 +763,9 @@
 
             final int faceId = faces.get(0).getBiometricId();
             final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext, mLazyDaemon,
-                    token, listener, userId, opPackageName, mSensorId, feature, faceId);
+                    token, listener, userId, opPackageName, mSensorId,
+                    BiometricLogger.ofUnknown(mContext), BiometricContext.getInstance(),
+                    feature, faceId);
             mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
                 @Override
                 public void onClientFinished(
@@ -767,7 +790,10 @@
 
             final List<Face> enrolledList = getEnrolledFaces(mSensorId, userId);
             final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext,
-                    mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId, enrolledList,
+                    mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    BiometricContext.getInstance(), enrolledList,
                     FaceUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
             mScheduler.scheduleClientMonitor(client, callback);
         });
@@ -890,7 +916,9 @@
         final boolean hasEnrolled = !getEnrolledFaces(mSensorId, targetUserId).isEmpty();
         final FaceUpdateActiveUserClient client = new FaceUpdateActiveUserClient(mContext,
                 mLazyDaemon, targetUserId, mContext.getOpPackageName(), mSensorId,
-                hasEnrolled, mAuthenticatorIds);
+                createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                        BiometricsProtoEnums.CLIENT_UNKNOWN),
+                BiometricContext.getInstance(), hasEnrolled, mAuthenticatorIds);
         mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
             @Override
             public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
@@ -904,6 +932,11 @@
         });
     }
 
+    private BiometricLogger createLogger(int statsAction, int statsClient) {
+        return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FACE,
+                statsAction, statsClient);
+    }
+
     /**
      * Sends a debug message to the HAL with the provided FileDescriptor and arguments.
      */
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
index 9038435..8d76e9f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
@@ -23,7 +23,6 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricFaceConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.face.V1_0.IBiometricsFace;
 import android.hardware.face.FaceManager;
 import android.os.IBinder;
@@ -32,6 +31,8 @@
 
 import com.android.internal.R;
 import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AuthenticationClient;
 import com.android.server.biometrics.sensors.BiometricNotificationUtils;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -66,12 +67,13 @@
             @NonNull IBinder token, long requestId,
             @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
             boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId,
-            boolean isStrongBiometric, int statsClient, @NonNull LockoutTracker lockoutTracker,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            boolean isStrongBiometric, @NonNull LockoutTracker lockoutTracker,
             @NonNull UsageStats usageStats, boolean allowBackgroundAuthentication,
             boolean isKeyguardBypassEnabled) {
         super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
-                owner, cookie, requireConfirmation, sensorId, isStrongBiometric,
-                BiometricsProtoEnums.MODALITY_FACE, statsClient, null /* taskStackListener */,
+                owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
+                isStrongBiometric, null /* taskStackListener */,
                 lockoutTracker, allowBackgroundAuthentication, true /* shouldVibrate */,
                 isKeyguardBypassEnabled);
         setRequestId(requestId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
index 92f7253..226e458 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
@@ -20,7 +20,6 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.biometrics.BiometricFaceConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.face.V1_0.IBiometricsFace;
 import android.hardware.biometrics.face.V1_0.Status;
 import android.hardware.face.Face;
@@ -32,6 +31,8 @@
 
 import com.android.internal.R;
 import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.BiometricUtils;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
@@ -58,10 +59,10 @@
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull byte[] hardwareAuthToken, @NonNull String owner, long requestId,
             @NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec,
-            @Nullable Surface previewSurface, int sensorId) {
+            @Nullable Surface previewSurface, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
         super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
-                timeoutSec, BiometricsProtoEnums.MODALITY_FACE, sensorId,
-                false /* shouldVibrate */);
+                timeoutSec, sensorId, false /* shouldVibrate */, logger, biometricContext);
         setRequestId(requestId);
         mDisabledFeatures = Arrays.copyOf(disabledFeatures, disabledFeatures.length);
         mEnrollIgnoreList = getContext().getResources()
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java
index b66ad60..97838a7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java
@@ -25,6 +25,8 @@
 import android.util.Slog;
 
 import com.android.internal.util.Preconditions;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.GenerateChallengeClient;
@@ -51,8 +53,10 @@
     FaceGenerateChallengeClient(@NonNull Context context,
             @NonNull Supplier<IBiometricsFace> lazyDaemon, @NonNull IBinder token,
             @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
-            int sensorId, long now) {
-        super(context, lazyDaemon, token, listener, userId, owner, sensorId);
+            int sensorId, @NonNull BiometricLogger logger,
+            @NonNull BiometricContext biometricContext, long now) {
+        super(context, lazyDaemon, token, listener, userId, owner, sensorId, logger,
+                biometricContext);
         mCreatedAt = now;
         mWaiting = new ArrayList<>();
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
index 1b387bf..9812536 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.face.V1_0.IBiometricsFace;
 import android.hardware.biometrics.face.V1_0.OptionalBool;
 import android.hardware.biometrics.face.V1_0.Status;
@@ -28,6 +27,8 @@
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.HalClientMonitor;
@@ -48,13 +49,13 @@
 
     FaceGetFeatureClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon,
             @NonNull IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId,
-            @NonNull String owner, int sensorId, int feature, int faceId) {
+            @NonNull String owner, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            int feature, int faceId) {
         super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
-                BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN,
-                BiometricsProtoEnums.CLIENT_UNKNOWN);
+                logger, biometricContext);
         mFeature = feature;
         mFaceId = faceId;
-
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java
index 93a2913..d21a750 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java
@@ -18,11 +18,12 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.face.V1_0.IBiometricsFace;
 import android.hardware.face.Face;
 import android.os.IBinder;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.BiometricUtils;
 import com.android.server.biometrics.sensors.InternalCleanupClient;
 import com.android.server.biometrics.sensors.InternalEnumerateClient;
@@ -40,29 +41,32 @@
 
     FaceInternalCleanupClient(@NonNull Context context,
             @NonNull Supplier<IBiometricsFace> lazyDaemon, int userId, @NonNull String owner,
-            int sensorId, @NonNull List<Face> enrolledList, @NonNull BiometricUtils<Face> utils,
-            @NonNull Map<Integer, Long> authenticatorIds) {
-        super(context, lazyDaemon, userId, owner, sensorId, BiometricsProtoEnums.MODALITY_FACE,
+            int sensorId, @NonNull BiometricLogger logger,
+            @NonNull BiometricContext biometricContext, @NonNull List<Face> enrolledList,
+            @NonNull BiometricUtils<Face> utils, @NonNull Map<Integer, Long> authenticatorIds) {
+        super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext,
                 enrolledList, utils, authenticatorIds);
     }
 
     @Override
     protected InternalEnumerateClient<IBiometricsFace> getEnumerateClient(Context context,
             Supplier<IBiometricsFace> lazyDaemon, IBinder token, int userId, String owner,
-            List<Face> enrolledList, BiometricUtils<Face> utils, int sensorId) {
+            List<Face> enrolledList, BiometricUtils<Face> utils, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
         return new FaceInternalEnumerateClient(context, lazyDaemon, token, userId, owner,
-                enrolledList, utils, sensorId);
+                enrolledList, utils, sensorId, logger, biometricContext);
     }
 
     @Override
     protected RemovalClient<Face, IBiometricsFace> getRemovalClient(Context context,
             Supplier<IBiometricsFace> lazyDaemon, IBinder token,
             int biometricId, int userId, String owner, BiometricUtils<Face> utils, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             Map<Integer, Long> authenticatorIds) {
         // Internal remove does not need to send results to anyone. Cleanup (enumerate + remove)
         // is all done internally.
         return new FaceRemovalClient(context, lazyDaemon, token,
                 null /* ClientMonitorCallbackConverter */, biometricId, userId, owner, utils,
-                sensorId, authenticatorIds);
+                sensorId, logger, biometricContext, authenticatorIds);
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java
index f1788de..250dd7e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java
@@ -18,13 +18,14 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.face.V1_0.IBiometricsFace;
 import android.hardware.face.Face;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.BiometricUtils;
 import com.android.server.biometrics.sensors.InternalEnumerateClient;
 
@@ -41,9 +42,10 @@
     FaceInternalEnumerateClient(@NonNull Context context,
             @NonNull Supplier<IBiometricsFace> lazyDaemon, @NonNull IBinder token, int userId,
             @NonNull String owner, @NonNull List<Face> enrolledList,
-            @NonNull BiometricUtils<Face> utils, int sensorId) {
+            @NonNull BiometricUtils<Face> utils, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
         super(context, lazyDaemon, token, userId, owner, enrolledList, utils, sensorId,
-                BiometricsProtoEnums.MODALITY_FACE);
+                logger, biometricContext);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java
index cbc23e4..0ee7a35 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java
@@ -18,13 +18,14 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.face.V1_0.IBiometricsFace;
 import android.hardware.face.Face;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.BiometricUtils;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.RemovalClient;
@@ -44,9 +45,11 @@
     FaceRemovalClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
             int biometricId, int userId, @NonNull String owner, @NonNull BiometricUtils<Face> utils,
-            int sensorId, @NonNull Map<Integer, Long> authenticatorIds) {
-        super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId,
-                authenticatorIds, BiometricsProtoEnums.MODALITY_FACE);
+            int sensorId, @NonNull BiometricLogger logger,
+            @NonNull BiometricContext biometricContext,
+            @NonNull Map<Integer, Long> authenticatorIds) {
+        super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId, logger,
+                biometricContext, authenticatorIds);
         mBiometricId = biometricId;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java
index 88e2318..6e74d36 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java
@@ -18,12 +18,13 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.face.V1_0.IBiometricsFace;
 import android.os.RemoteException;
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.HalClientMonitor;
 
@@ -42,10 +43,10 @@
 
     FaceResetLockoutClient(@NonNull Context context,
             @NonNull Supplier<IBiometricsFace> lazyDaemon, int userId, String owner, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             @NonNull byte[] hardwareAuthToken) {
         super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
-                0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
-                BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+                0 /* cookie */, sensorId, logger, biometricContext);
 
         mHardwareAuthToken = new ArrayList<>();
         for (byte b : hardwareAuthToken) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java
index ab8d161..b7b0dc04 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java
@@ -23,6 +23,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.RevokeChallengeClient;
 
 import java.util.function.Supplier;
@@ -37,8 +39,9 @@
 
     FaceRevokeChallengeClient(@NonNull Context context,
             @NonNull Supplier<IBiometricsFace> lazyDaemon, @NonNull IBinder token,
-            int userId, @NonNull String owner, int sensorId) {
-        super(context, lazyDaemon, token, userId, owner, sensorId);
+            int userId, @NonNull String owner, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
+        super(context, lazyDaemon, token, userId, owner, sensorId, logger, biometricContext);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java
index b2b52e7..3c82f9c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.face.V1_0.IBiometricsFace;
 import android.hardware.biometrics.face.V1_0.Status;
 import android.os.IBinder;
@@ -26,6 +25,8 @@
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.HalClientMonitor;
@@ -48,11 +49,11 @@
 
     FaceSetFeatureClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
-            @NonNull String owner, int sensorId, int feature, boolean enabled,
-            byte[] hardwareAuthToken, int faceId) {
+            @NonNull String owner, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            int feature, boolean enabled, byte[] hardwareAuthToken, int faceId) {
         super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
-                BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN,
-                BiometricsProtoEnums.CLIENT_UNKNOWN);
+                logger, biometricContext);
         mFeature = feature;
         mEnabled = enabled;
         mFaceId = faceId;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java
index 04b9327..8385c3f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java
@@ -18,13 +18,14 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.face.V1_0.IBiometricsFace;
 import android.os.Environment;
 import android.os.RemoteException;
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.HalClientMonitor;
 
@@ -41,11 +42,11 @@
 
     FaceUpdateActiveUserClient(@NonNull Context context,
             @NonNull Supplier<IBiometricsFace> lazyDaemon, int userId, @NonNull String owner,
-            int sensorId, boolean hasEnrolledBiometrics,
+            int sensorId, @NonNull BiometricLogger logger,
+            @NonNull BiometricContext biometricContext, boolean hasEnrolledBiometrics,
             @NonNull Map<Integer, Long> authenticatorIds) {
         super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
-                0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
-                BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+                0 /* cookie */, sensorId, logger, biometricContext);
         mHasEnrolledBiometrics = hasEnrolledBiometrics;
         mAuthenticatorIds = authenticatorIds;
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java
index 727101a..55861bb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java
@@ -16,11 +16,11 @@
 
 package com.android.server.biometrics.sensors.fingerprint.aidl;
 
+import static com.android.server.biometrics.sensors.fingerprint.aidl.Sensor.HalSessionCallback;
+
 import android.annotation.NonNull;
 import android.hardware.biometrics.fingerprint.ISession;
 
-import static com.android.server.biometrics.sensors.fingerprint.aidl.Sensor.HalSessionCallback;
-
 /**
  * A holder for an AIDL {@link ISession} with additional metadata about the current user
  * and the backend.
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 2c1c80c..184e1ea 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
@@ -23,9 +23,7 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricFingerprintConstants;
 import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired;
-import android.hardware.biometrics.BiometricsProtoEnums;
 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.FingerprintSensorPropertiesInternal;
@@ -35,6 +33,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+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.AuthenticationClient;
@@ -72,15 +72,18 @@
             @NonNull IBinder token, long requestId,
             @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
             boolean restricted, @NonNull String owner, int cookie, boolean requireConfirmation,
-            int sensorId, boolean isStrongBiometric, int statsClient,
+            int sensorId,
+            @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
+            boolean isStrongBiometric,
             @Nullable TaskStackListener taskStackListener, @NonNull LockoutCache lockoutCache,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
             @Nullable ISidefpsController sidefpsController,
             boolean allowBackgroundAuthentication,
             @NonNull FingerprintSensorPropertiesInternal sensorProps) {
         super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted, owner,
-                cookie, requireConfirmation, sensorId, isStrongBiometric,
-                BiometricsProtoEnums.MODALITY_FINGERPRINT, statsClient, taskStackListener,
+                cookie, requireConfirmation, sensorId,
+                biometricLogger, biometricContext,
+                isStrongBiometric, taskStackListener,
                 lockoutCache, allowBackgroundAuthentication, true /* shouldVibrate */,
                 false /* isKeyguardBypassEnabled */);
         setRequestId(requestId);
@@ -175,13 +178,12 @@
         final AidlSession session = getFreshDaemon();
 
         if (session.hasContextMethods()) {
-            final OperationContext context = new OperationContext();
-            // TODO: add reason, id, and isAoD
-            context.id = 0;
-            context.reason = OperationReason.UNKNOWN;
-            context.isAoD = false;
-            context.isCrypto = isCryptoOperation();
-            return session.getSession().authenticateWithContext(mOperationId, context);
+            // TODO: add reason, id
+            mOperationContext.id = 0;
+            mOperationContext.reason = OperationReason.UNKNOWN;
+            mOperationContext.isAoD = getBiometricContext().isAoD();
+            mOperationContext.isCrypto = isCryptoOperation();
+            return session.getSession().authenticateWithContext(mOperationId, mOperationContext);
         } else {
             return session.getSession().authenticate(mOperationId);
         }
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 6645332..9d348e1 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
@@ -20,7 +20,6 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.biometrics.BiometricOverlayConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.common.ICancellationSignal;
 import android.hardware.biometrics.common.OperationContext;
 import android.hardware.biometrics.common.OperationReason;
@@ -30,6 +29,8 @@
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AcquisitionClient;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
@@ -54,11 +55,10 @@
             @NonNull IBinder token, long requestId,
             @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull String owner, int sensorId,
-            @Nullable IUdfpsOverlayController udfpsOverlayController, boolean isStrongBiometric,
-            int statsClient) {
+            @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
+            @Nullable IUdfpsOverlayController udfpsOverlayController, boolean isStrongBiometric) {
         super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
-                true /* shouldVibrate */, BiometricsProtoEnums.MODALITY_FINGERPRINT,
-                BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient);
+                true /* shouldVibrate */, biometricLogger, biometricContext);
         setRequestId(requestId);
         mIsStrongBiometric = isStrongBiometric;
         mSensorOverlays = new SensorOverlays(udfpsOverlayController, null /* sideFpsController*/);
@@ -100,10 +100,10 @@
 
         if (session.hasContextMethods()) {
             final OperationContext context = new OperationContext();
-            // TODO: add reason, id, and isAoD
+            // TODO: add reason, id
             context.id = 0;
             context.reason = OperationReason.UNKNOWN;
-            context.isAoD = false;
+            context.isAoD = getBiometricContext().isAoD();
             context.isCrypto = isCryptoOperation();
             return session.getSession().detectInteractionWithContext(context);
         } else {
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 d0c5bb8..ed16a6d 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
@@ -22,7 +22,6 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricFingerprintConstants;
 import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.common.ICancellationSignal;
 import android.hardware.biometrics.common.OperationContext;
 import android.hardware.biometrics.common.OperationReason;
@@ -38,6 +37,8 @@
 import android.util.Slog;
 
 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.sensors.BiometricNotificationUtils;
 import com.android.server.biometrics.sensors.BiometricUtils;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -68,14 +69,15 @@
             @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull byte[] hardwareAuthToken, @NonNull String owner,
             @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
             @Nullable ISidefpsController sidefpsController,
             int maxTemplatesPerUser, @FingerprintManager.EnrollReason int enrollReason) {
         // UDFPS haptics occur when an image is acquired (instead of when the result is known)
         super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
-                0 /* timeoutSec */, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId,
-                !sensorProps.isAnyUdfpsType() /* shouldVibrate */);
+                0 /* timeoutSec */, sensorId,
+                !sensorProps.isAnyUdfpsType() /* shouldVibrate */, logger, biometricContext);
         setRequestId(requestId);
         mSensorProps = sensorProps;
         mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
@@ -177,10 +179,10 @@
 
         if (session.hasContextMethods()) {
             final OperationContext context = new OperationContext();
-            // TODO: add reason, id, and isAoD
+            // TODO: add reason, id
             context.id = 0;
             context.reason = OperationReason.UNKNOWN;
-            context.isAoD = false;
+            context.isAoD = getBiometricContext().isAoD();
             context.isCrypto = isCryptoOperation();
             return session.getSession().enrollWithContext(hat, context);
         } else {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
index 04a7ca0..ddae8be 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
@@ -23,6 +23,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.GenerateChallengeClient;
 
@@ -38,8 +40,10 @@
             @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token,
             @NonNull ClientMonitorCallbackConverter listener,
-            int userId, @NonNull String owner, int sensorId) {
-        super(context, lazyDaemon, token, listener, userId, owner, sensorId);
+            int userId, @NonNull String owner, int sensorId,
+            @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext) {
+        super(context, lazyDaemon, token, listener, userId, owner, sensorId,
+                biometricLogger, biometricContext);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
index 3a487fc..ea1a622 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
@@ -18,11 +18,12 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.os.RemoteException;
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.HalClientMonitor;
 
@@ -36,11 +37,12 @@
     private final Map<Integer, Long> mAuthenticatorIds;
 
     FingerprintGetAuthenticatorIdClient(@NonNull Context context,
-            @NonNull Supplier<AidlSession> lazyDaemon, int userId, @NonNull String owner,
-            int sensorId, Map<Integer, Long> authenticatorIds) {
+            @NonNull Supplier<AidlSession> lazyDaemon,
+            int userId, @NonNull String owner, int sensorId,
+            @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
+            Map<Integer, Long> authenticatorIds) {
         super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
-                0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_FINGERPRINT,
-                BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+                0 /* cookie */, sensorId, biometricLogger, biometricContext);
         mAuthenticatorIds = authenticatorIds;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
index 0ecad72..09bdd6d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
@@ -22,6 +22,8 @@
 import android.hardware.fingerprint.Fingerprint;
 import android.os.IBinder;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.BiometricUtils;
 import com.android.server.biometrics.sensors.InternalCleanupClient;
 import com.android.server.biometrics.sensors.InternalEnumerateClient;
@@ -39,28 +41,35 @@
 class FingerprintInternalCleanupClient extends InternalCleanupClient<Fingerprint, AidlSession> {
 
     FingerprintInternalCleanupClient(@NonNull Context context,
-            @NonNull Supplier<AidlSession> lazyDaemon, int userId, @NonNull String owner,
-            int sensorId, @NonNull List<Fingerprint> enrolledList,
+            @NonNull Supplier<AidlSession> lazyDaemon,
+            int userId, @NonNull String owner, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            @NonNull List<Fingerprint> enrolledList,
             @NonNull FingerprintUtils utils, @NonNull Map<Integer, Long> authenticatorIds) {
-        super(context, lazyDaemon, userId, owner, sensorId,
-                BiometricsProtoEnums.MODALITY_FINGERPRINT, enrolledList, utils, authenticatorIds);
+        super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext,
+                enrolledList, utils, authenticatorIds);
     }
 
     @Override
     protected InternalEnumerateClient<AidlSession> getEnumerateClient(Context context,
             Supplier<AidlSession> lazyDaemon, IBinder token, int userId, String owner,
-            List<Fingerprint> enrolledList, BiometricUtils<Fingerprint> utils, int sensorId) {
+            List<Fingerprint> enrolledList, BiometricUtils<Fingerprint> utils, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
         return new FingerprintInternalEnumerateClient(context, lazyDaemon, token, userId, owner,
-                enrolledList, utils, sensorId);
+                enrolledList, utils, sensorId,
+                logger.swapAction(context, BiometricsProtoEnums.ACTION_ENUMERATE),
+                biometricContext);
     }
 
     @Override
     protected RemovalClient<Fingerprint, AidlSession> getRemovalClient(Context context,
             Supplier<AidlSession> lazyDaemon, IBinder token, int biometricId, int userId,
             String owner, BiometricUtils<Fingerprint> utils, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             Map<Integer, Long> authenticatorIds) {
         return new FingerprintRemovalClient(context, lazyDaemon, token,
                 null /* ClientMonitorCallbackConverter */, new int[] {biometricId}, userId, owner,
-                utils, sensorId, authenticatorIds);
+                utils, sensorId, logger.swapAction(context, BiometricsProtoEnums.ACTION_REMOVE),
+                biometricContext, authenticatorIds);
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClient.java
index 06ba6d4..a5a832a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClient.java
@@ -18,12 +18,13 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.fingerprint.Fingerprint;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.BiometricUtils;
 import com.android.server.biometrics.sensors.InternalEnumerateClient;
 
@@ -40,9 +41,10 @@
     protected FingerprintInternalEnumerateClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, int userId,
             @NonNull String owner, @NonNull List<Fingerprint> enrolledList,
-            @NonNull BiometricUtils<Fingerprint> utils, int sensorId) {
+            @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
         super(context, lazyDaemon, token, userId, owner, enrolledList, utils, sensorId,
-                BiometricsProtoEnums.MODALITY_FINGERPRINT);
+                logger, biometricContext);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInvalidationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInvalidationClient.java
index 1ee32e9..bc02897 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInvalidationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInvalidationClient.java
@@ -23,6 +23,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.InvalidationClient;
 
 import java.util.Map;
@@ -33,8 +35,10 @@
 
     public FingerprintInvalidationClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon, int userId, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             @NonNull Map<Integer, Long> authenticatorIds, @NonNull IInvalidationCallback callback) {
-        super(context, lazyDaemon, userId, sensorId, authenticatorIds, callback);
+        super(context, lazyDaemon, userId, sensorId, logger, biometricContext,
+                authenticatorIds, callback);
     }
 
     @Override
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 efc9304..221ff941 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
@@ -26,6 +26,7 @@
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.content.res.TypedArray;
+import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.ComponentInfoInternal;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
@@ -53,6 +54,8 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AuthenticationClient;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -298,6 +301,9 @@
                     new FingerprintGetAuthenticatorIdClient(mContext,
                             mSensors.get(sensorId).getLazySession(), userId,
                             mContext.getOpPackageName(), sensorId,
+                            createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                                    BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricContext.getInstance(),
                             mSensors.get(sensorId).getAuthenticatorIds());
             scheduleForSensor(sensorId, client);
         });
@@ -307,6 +313,7 @@
         mHandler.post(() -> {
             final InvalidationRequesterClient<Fingerprint> client =
                     new InvalidationRequesterClient<>(mContext, userId, sensorId,
+                            BiometricLogger.ofUnknown(mContext), BiometricContext.getInstance(),
                             FingerprintUtils.getInstance(sensorId));
             scheduleForSensor(sensorId, client);
         });
@@ -317,7 +324,10 @@
         mHandler.post(() -> {
             final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(
                     mContext, mSensors.get(sensorId).getLazySession(), userId,
-                    mContext.getOpPackageName(), sensorId, hardwareAuthToken,
+                    mContext.getOpPackageName(), sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    BiometricContext.getInstance(), hardwareAuthToken,
                     mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher);
             scheduleForSensor(sensorId, client);
         });
@@ -331,7 +341,9 @@
                     new FingerprintGenerateChallengeClient(mContext,
                             mSensors.get(sensorId).getLazySession(), token,
                             new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
-                            sensorId);
+                            sensorId, createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                                BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricContext.getInstance());
             scheduleForSensor(sensorId, client);
         });
     }
@@ -343,7 +355,10 @@
             final FingerprintRevokeChallengeClient client =
                     new FingerprintRevokeChallengeClient(mContext,
                             mSensors.get(sensorId).getLazySession(), token,
-                            userId, opPackageName, sensorId, challenge);
+                            userId, opPackageName, sensorId,
+                            createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                                    BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricContext.getInstance(), challenge);
             scheduleForSensor(sensorId, client);
         });
     }
@@ -361,6 +376,9 @@
                     mSensors.get(sensorId).getLazySession(), token, id,
                     new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
                     opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_ENROLL,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    BiometricContext.getInstance(),
                     mSensors.get(sensorId).getSensorProperties(),
                     mUdfpsOverlayController, mSidefpsController, maxTemplatesPerUser, enrollReason);
             scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
@@ -399,8 +417,10 @@
             final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
             final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
                     mSensors.get(sensorId).getLazySession(), token, id, callback, userId,
-                    opPackageName, sensorId, mUdfpsOverlayController, isStrongBiometric,
-                    statsClient);
+                    opPackageName, sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+                    BiometricContext.getInstance(),
+                    mUdfpsOverlayController, isStrongBiometric);
             scheduleForSensor(sensorId, client, mFingerprintStateCallback);
         });
 
@@ -417,7 +437,9 @@
             final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
                     mContext, mSensors.get(sensorId).getLazySession(), token, requestId, callback,
                     userId, operationId, restricted, opPackageName, cookie,
-                    false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
+                    false /* requireConfirmation */, sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+                    BiometricContext.getInstance(), isStrongBiometric,
                     mTaskStackListener, mSensors.get(sensorId).getLockoutCache(),
                     mUdfpsOverlayController, mSidefpsController, allowBackgroundAuthentication,
                     mSensors.get(sensorId).getSensorProperties());
@@ -479,6 +501,9 @@
                     mSensors.get(sensorId).getLazySession(), token,
                     new ClientMonitorCallbackConverter(receiver), fingerprintIds, userId,
                     opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    BiometricContext.getInstance(),
                     mSensors.get(sensorId).getAuthenticatorIds());
             scheduleForSensor(sensorId, client, mFingerprintStateCallback);
         });
@@ -492,14 +517,22 @@
             final FingerprintInternalCleanupClient client =
                     new FingerprintInternalCleanupClient(mContext,
                             mSensors.get(sensorId).getLazySession(), userId,
-                            mContext.getOpPackageName(), sensorId, enrolledList,
-                            FingerprintUtils.getInstance(sensorId),
+                            mContext.getOpPackageName(), sensorId,
+                            createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
+                                    BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricContext.getInstance(),
+                            enrolledList, FingerprintUtils.getInstance(sensorId),
                             mSensors.get(sensorId).getAuthenticatorIds());
             scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(callback,
                     mFingerprintStateCallback));
         });
     }
 
+    private BiometricLogger createLogger(int statsAction, int statsClient) {
+        return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FINGERPRINT,
+                statsAction, statsClient);
+    }
+
     @Override
     public boolean isHardwareDetected(int sensorId) {
         return hasHalInstance();
@@ -524,6 +557,9 @@
             final FingerprintInvalidationClient client =
                     new FingerprintInvalidationClient(mContext,
                             mSensors.get(sensorId).getLazySession(), userId, sensorId,
+                            createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                                    BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricContext.getInstance(),
                             mSensors.get(sensorId).getAuthenticatorIds(), callback);
             scheduleForSensor(sensorId, client);
         });
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java
index fbc1dc0..d559bb1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java
@@ -19,12 +19,13 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.fingerprint.Fingerprint;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.BiometricUtils;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.RemovalClient;
@@ -45,9 +46,10 @@
             @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token,
             @Nullable ClientMonitorCallbackConverter listener, int[] biometricIds, int userId,
             @NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             @NonNull Map<Integer, Long> authenticatorIds) {
         super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId,
-                authenticatorIds, BiometricsProtoEnums.MODALITY_FINGERPRINT);
+                logger, biometricContext, authenticatorIds);
         mBiometricIds = biometricIds;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
index 0e64dab..f90cba7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.fingerprint.IFingerprint;
 import android.hardware.keymaster.HardwareAuthToken;
 import android.os.RemoteException;
@@ -26,6 +25,8 @@
 
 import com.android.server.biometrics.BiometricsProto;
 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.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ErrorConsumer;
 import com.android.server.biometrics.sensors.HalClientMonitor;
@@ -50,11 +51,11 @@
 
     FingerprintResetLockoutClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon, int userId, String owner, int sensorId,
+            @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
             @NonNull byte[] hardwareAuthToken, @NonNull LockoutCache lockoutTracker,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
         super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
-                0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
-                BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+                0 /* cookie */, sensorId, biometricLogger, biometricContext);
         mHardwareAuthToken = HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken);
         mLockoutCache = lockoutTracker;
         mLockoutResetDispatcher = lockoutResetDispatcher;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java
index fd93867..afa62e2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java
@@ -23,6 +23,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.RevokeChallengeClient;
 
 import java.util.function.Supplier;
@@ -38,8 +40,10 @@
 
     FingerprintRevokeChallengeClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token,
-            int userId, @NonNull String owner, int sensorId, long challenge) {
-        super(context, lazyDaemon, token, userId, owner, sensorId);
+            int userId, @NonNull String owner, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            long challenge) {
+        super(context, lazyDaemon, token, userId, owner, sensorId, logger, biometricContext);
         mChallenge = challenge;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java
index 9dc06e1..52305a3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java
@@ -27,6 +27,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.StartUserClient;
 
@@ -40,9 +42,10 @@
     public FingerprintStartUserClient(@NonNull Context context,
             @NonNull Supplier<IFingerprint> lazyDaemon,
             @Nullable IBinder token, int userId, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             @NonNull ISessionCallback sessionCallback,
             @NonNull UserStartedCallback<ISession> callback) {
-        super(context, lazyDaemon, token, userId, sensorId, callback);
+        super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback);
         mSessionCallback = sessionCallback;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java
index fac17f2c..2cc1879 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java
@@ -23,6 +23,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.StopUserClient;
 
@@ -33,8 +35,10 @@
 
     public FingerprintStopUserClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon, @Nullable IBinder token, int userId,
-            int sensorId, @NonNull UserStoppedCallback callback) {
-        super(context, lazyDaemon, token, userId, sensorId, callback);
+            int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            @NonNull UserStoppedCallback callback) {
+        super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback);
     }
 
     @Override
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 2276232..27226b3 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
@@ -39,12 +39,15 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.biometrics.HardwareAuthTokenUtils;
 import com.android.server.biometrics.SensorServiceStateProto;
 import com.android.server.biometrics.SensorStateProto;
 import com.android.server.biometrics.UserStateProto;
 import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AcquisitionClient;
 import com.android.server.biometrics.sensors.AuthenticationConsumer;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
@@ -72,7 +75,7 @@
  * {@link android.hardware.biometrics.fingerprint.IFingerprint} HAL.
  */
 @SuppressWarnings("deprecation")
-class Sensor {
+public class Sensor {
 
     private boolean mTestHalEnabled;
 
@@ -89,7 +92,8 @@
     @Nullable private AidlSession mCurrentSession;
     @NonNull private final Supplier<AidlSession> mLazySession;
 
-    static class HalSessionCallback extends ISessionCallback.Stub {
+    @VisibleForTesting
+    public static class HalSessionCallback extends ISessionCallback.Stub {
 
         /**
          * Interface to sends results to the HalSessionCallback's owner.
@@ -442,7 +446,9 @@
                     @Override
                     public StopUserClient<?> getStopUserClient(int userId) {
                         return new FingerprintStopUserClient(mContext, mLazySession, mToken,
-                                userId, mSensorProperties.sensorId, () -> mCurrentSession = null);
+                                userId, mSensorProperties.sensorId,
+                                BiometricLogger.ofUnknown(mContext), BiometricContext.getInstance(),
+                                () -> mCurrentSession = null);
                     }
 
                     @NonNull
@@ -478,6 +484,7 @@
 
                         return new FingerprintStartUserClient(mContext, provider::getHalInstance,
                                 mToken, newUserId, mSensorProperties.sensorId,
+                                BiometricLogger.ofUnknown(mContext), BiometricContext.getInstance(),
                                 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 29d460f..6c35c8c 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
@@ -57,6 +57,8 @@
 import com.android.server.biometrics.fingerprint.FingerprintServiceDumpProto;
 import com.android.server.biometrics.fingerprint.FingerprintUserStatsProto;
 import com.android.server.biometrics.fingerprint.PerformanceStatsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AcquisitionClient;
 import com.android.server.biometrics.sensors.AuthenticationClient;
 import com.android.server.biometrics.sensors.AuthenticationConsumer;
@@ -493,6 +495,9 @@
         final FingerprintUpdateActiveUserClient client =
                 new FingerprintUpdateActiveUserClient(mContext, mLazyDaemon, targetUserId,
                         mContext.getOpPackageName(), mSensorProperties.sensorId,
+                        createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                                BiometricsProtoEnums.CLIENT_UNKNOWN),
+                        BiometricContext.getInstance(),
                         this::getCurrentUser, hasEnrolled, mAuthenticatorIds, force);
         mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
             @Override
@@ -536,7 +541,10 @@
         // thread.
         mHandler.post(() -> {
             final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(mContext,
-                    userId, mContext.getOpPackageName(), sensorId, mLockoutTracker);
+                    userId, mContext.getOpPackageName(), sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    BiometricContext.getInstance(), mLockoutTracker);
             mScheduler.scheduleClientMonitor(client);
         });
     }
@@ -548,7 +556,10 @@
             final FingerprintGenerateChallengeClient client =
                     new FingerprintGenerateChallengeClient(mContext, mLazyDaemon, token,
                             new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
-                            mSensorProperties.sensorId);
+                            mSensorProperties.sensorId,
+                            createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                                    BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricContext.getInstance());
             mScheduler.scheduleClientMonitor(client);
         });
     }
@@ -559,7 +570,10 @@
         mHandler.post(() -> {
             final FingerprintRevokeChallengeClient client = new FingerprintRevokeChallengeClient(
                     mContext, mLazyDaemon, token, userId, opPackageName,
-                    mSensorProperties.sensorId);
+                    mSensorProperties.sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    BiometricContext.getInstance());
             mScheduler.scheduleClientMonitor(client);
         });
     }
@@ -577,7 +591,11 @@
                     mLazyDaemon, token, id, new ClientMonitorCallbackConverter(receiver),
                     userId, hardwareAuthToken, opPackageName,
                     FingerprintUtils.getLegacyInstance(mSensorId), ENROLL_TIMEOUT_SEC,
-                    mSensorProperties.sensorId, mUdfpsOverlayController, mSidefpsController,
+                    mSensorProperties.sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_ENROLL,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    BiometricContext.getInstance(),
+                    mUdfpsOverlayController, mSidefpsController,
                     enrollReason);
             mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
                 @Override
@@ -616,8 +634,9 @@
             final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId);
             final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
                     mLazyDaemon, token, id, listener, userId, opPackageName,
-                    mSensorProperties.sensorId, mUdfpsOverlayController, isStrongBiometric,
-                    statsClient);
+                    mSensorProperties.sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+                    BiometricContext.getInstance(), mUdfpsOverlayController, isStrongBiometric);
             mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback);
         });
 
@@ -636,7 +655,9 @@
             final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
                     mContext, mLazyDaemon, token, requestId, listener, userId, operationId,
                     restricted, opPackageName, cookie, false /* requireConfirmation */,
-                    mSensorProperties.sensorId, isStrongBiometric, statsClient,
+                    mSensorProperties.sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+                    BiometricContext.getInstance(), isStrongBiometric,
                     mTaskStackListener, mLockoutTracker,
                     mUdfpsOverlayController, mSidefpsController,
                     allowBackgroundAuthentication, mSensorProperties);
@@ -678,7 +699,10 @@
             final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext,
                     mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), fingerId,
                     userId, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId),
-                    mSensorProperties.sensorId, mAuthenticatorIds);
+                    mSensorProperties.sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    BiometricContext.getInstance(), mAuthenticatorIds);
             mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback);
         });
     }
@@ -695,7 +719,10 @@
                     mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver),
                     0 /* fingerprintId */, userId, opPackageName,
                     FingerprintUtils.getLegacyInstance(mSensorId),
-                    mSensorProperties.sensorId, mAuthenticatorIds);
+                    mSensorProperties.sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    BiometricContext.getInstance(), mAuthenticatorIds);
             mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback);
         });
     }
@@ -709,7 +736,10 @@
                     mSensorProperties.sensorId, userId);
             final FingerprintInternalCleanupClient client = new FingerprintInternalCleanupClient(
                     mContext, mLazyDaemon, userId, mContext.getOpPackageName(),
-                    mSensorProperties.sensorId, enrolledList,
+                    mSensorProperties.sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    BiometricContext.getInstance(), enrolledList,
                     FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
             mScheduler.scheduleClientMonitor(client, callback);
         });
@@ -722,6 +752,11 @@
                 mFingerprintStateCallback));
     }
 
+    private BiometricLogger createLogger(int statsAction, int statsClient) {
+        return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FINGERPRINT,
+                statsAction, statsClient);
+    }
+
     @Override
     public boolean isHardwareDetected(int sensorId) {
         return getDaemon() != null;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 589bfcf..97fbb5f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -23,7 +23,6 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricFingerprintConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.ISidefpsController;
@@ -32,6 +31,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+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.AuthenticationClient;
@@ -69,7 +70,8 @@
             @NonNull IBinder token, long requestId,
             @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
             boolean restricted, @NonNull String owner, int cookie, boolean requireConfirmation,
-            int sensorId, boolean isStrongBiometric, int statsClient,
+            int sensorId, @NonNull BiometricLogger logger,
+            @NonNull BiometricContext biometricContext, boolean isStrongBiometric,
             @NonNull TaskStackListener taskStackListener,
             @NonNull LockoutFrameworkImpl lockoutTracker,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
@@ -77,10 +79,9 @@
             boolean allowBackgroundAuthentication,
             @NonNull FingerprintSensorPropertiesInternal sensorProps) {
         super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
-                owner, cookie, requireConfirmation, sensorId, isStrongBiometric,
-                BiometricsProtoEnums.MODALITY_FINGERPRINT, statsClient, taskStackListener,
-                lockoutTracker, allowBackgroundAuthentication, true /* shouldVibrate */,
-                false /* isKeyguardBypassEnabled */);
+                owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
+                isStrongBiometric, taskStackListener, lockoutTracker, allowBackgroundAuthentication,
+                true /* shouldVibrate */, false /* isKeyguardBypassEnabled */);
         setRequestId(requestId);
         mLockoutFrameworkImpl = lockoutTracker;
         mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
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 8848746..f10d4e4 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
@@ -22,7 +22,6 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricFingerprintConstants;
 import android.hardware.biometrics.BiometricOverlayConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
@@ -30,6 +29,8 @@
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AcquisitionClient;
 import com.android.server.biometrics.sensors.AuthenticationConsumer;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -59,11 +60,11 @@
             @NonNull Supplier<IBiometricsFingerprint> lazyDaemon,
             @NonNull IBinder token, long requestId,
             @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
-            int sensorId, @Nullable IUdfpsOverlayController udfpsOverlayController,
-            boolean isStrongBiometric, int statsClient) {
+            int sensorId,
+            @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
+            @Nullable IUdfpsOverlayController udfpsOverlayController, boolean isStrongBiometric) {
         super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
-                true /* shouldVibrate */, BiometricsProtoEnums.MODALITY_FINGERPRINT,
-                BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient);
+                true /* shouldVibrate */, biometricLogger, biometricContext);
         setRequestId(requestId);
         mSensorOverlays = new SensorOverlays(udfpsOverlayController, null /* sideFpsController */);
         mIsStrongBiometric = isStrongBiometric;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
index c69deac..1d478e5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
@@ -21,7 +21,6 @@
 import android.content.Context;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricFingerprintConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintManager;
@@ -31,6 +30,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.BiometricNotificationUtils;
 import com.android.server.biometrics.sensors.BiometricUtils;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -62,12 +63,13 @@
             long requestId, @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull byte[] hardwareAuthToken, @NonNull String owner,
             @NonNull BiometricUtils<Fingerprint> utils, int timeoutSec, int sensorId,
+            @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
             @Nullable ISidefpsController sidefpsController,
             @FingerprintManager.EnrollReason int enrollReason) {
         super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
-                timeoutSec, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId,
-                true /* shouldVibrate */);
+                timeoutSec, sensorId, true /* shouldVibrate */, biometricLogger,
+                biometricContext);
         setRequestId(requestId);
         mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java
index 591f542..3bb7135 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java
@@ -23,6 +23,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.GenerateChallengeClient;
 
@@ -41,8 +43,10 @@
     FingerprintGenerateChallengeClient(@NonNull Context context,
             @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
             @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
-            int sensorId) {
-        super(context, lazyDaemon, token, listener, userId, owner, sensorId);
+            int sensorId, @NonNull BiometricLogger logger,
+            @NonNull BiometricContext biometricContext) {
+        super(context, lazyDaemon, token, listener, userId, owner, sensorId, logger,
+                biometricContext);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java
index 403602b..5e7cf35 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java
@@ -18,11 +18,12 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.hardware.fingerprint.Fingerprint;
 import android.os.IBinder;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.BiometricUtils;
 import com.android.server.biometrics.sensors.InternalCleanupClient;
 import com.android.server.biometrics.sensors.InternalEnumerateClient;
@@ -42,31 +43,35 @@
 
     FingerprintInternalCleanupClient(@NonNull Context context,
             @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, int userId,
-            @NonNull String owner, int sensorId, @NonNull List<Fingerprint> enrolledList,
+            @NonNull String owner, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            @NonNull List<Fingerprint> enrolledList,
             @NonNull BiometricUtils<Fingerprint> utils,
             @NonNull Map<Integer, Long> authenticatorIds) {
-        super(context, lazyDaemon, userId, owner, sensorId,
-                BiometricsProtoEnums.MODALITY_FINGERPRINT, enrolledList, utils, authenticatorIds);
+        super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext,
+                enrolledList, utils, authenticatorIds);
     }
 
     @Override
     protected InternalEnumerateClient<IBiometricsFingerprint> getEnumerateClient(
             Context context, Supplier<IBiometricsFingerprint> lazyDaemon, IBinder token,
             int userId, String owner, List<Fingerprint> enrolledList,
-            BiometricUtils<Fingerprint> utils, int sensorId) {
+            BiometricUtils<Fingerprint> utils, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
         return new FingerprintInternalEnumerateClient(context, lazyDaemon, token, userId, owner,
-                enrolledList, utils, sensorId);
+                enrolledList, utils, sensorId, logger, biometricContext);
     }
 
     @Override
     protected RemovalClient<Fingerprint, IBiometricsFingerprint> getRemovalClient(Context context,
             Supplier<IBiometricsFingerprint> lazyDaemon, IBinder token,
             int biometricId, int userId, String owner, BiometricUtils<Fingerprint> utils,
-            int sensorId, Map<Integer, Long> authenticatorIds) {
+            int sensorId, @NonNull BiometricLogger logger,
+            @NonNull BiometricContext biometricContext, Map<Integer, Long> authenticatorIds) {
         // Internal remove does not need to send results to anyone. Cleanup (enumerate + remove)
         // is all done internally.
         return new FingerprintRemovalClient(context, lazyDaemon, token,
                 null /* ClientMonitorCallbackConverter */, biometricId, userId, owner, utils,
-                sensorId, authenticatorIds);
+                sensorId, logger, biometricContext, authenticatorIds);
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalEnumerateClient.java
index def8ed0..0840f1b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalEnumerateClient.java
@@ -18,13 +18,14 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.hardware.fingerprint.Fingerprint;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.BiometricUtils;
 import com.android.server.biometrics.sensors.InternalEnumerateClient;
 
@@ -42,9 +43,10 @@
     FingerprintInternalEnumerateClient(@NonNull Context context,
             @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
             int userId, @NonNull String owner, @NonNull List<Fingerprint> enrolledList,
-            @NonNull BiometricUtils<Fingerprint> utils, int sensorId) {
+            @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
         super(context, lazyDaemon, token, userId, owner, enrolledList, utils, sensorId,
-                BiometricsProtoEnums.MODALITY_FINGERPRINT);
+                logger, biometricContext);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java
index 77c201c..9ec56c2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java
@@ -18,13 +18,14 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.hardware.fingerprint.Fingerprint;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.BiometricUtils;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.RemovalClient;
@@ -46,9 +47,10 @@
             @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
             @NonNull ClientMonitorCallbackConverter listener, int biometricId, int userId,
             @NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             @NonNull Map<Integer, Long> authenticatorIds) {
         super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId,
-                authenticatorIds, BiometricsProtoEnums.MODALITY_FINGERPRINT);
+                logger, biometricContext, authenticatorIds);
         mBiometricId = biometricId;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java
index ed28e3f..559ca06 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java
@@ -18,9 +18,10 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 
@@ -33,10 +34,11 @@
     @NonNull final LockoutFrameworkImpl mLockoutTracker;
 
     public FingerprintResetLockoutClient(@NonNull Context context, int userId,
-            @NonNull String owner, int sensorId, @NonNull LockoutFrameworkImpl lockoutTracker) {
+            @NonNull String owner, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            @NonNull LockoutFrameworkImpl lockoutTracker) {
         super(context, null /* token */, null /* listener */, userId, owner, 0 /* cookie */,
-                sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
-                BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+                sensorId, logger, biometricContext);
         mLockoutTracker = lockoutTracker;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java
index 0180a46..6273417 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java
@@ -23,6 +23,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.RevokeChallengeClient;
 
 import java.util.function.Supplier;
@@ -39,8 +41,9 @@
 
     FingerprintRevokeChallengeClient(@NonNull Context context,
             @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
-            int userId, @NonNull String owner, int sensorId) {
-        super(context, lazyDaemon, token, userId, owner, sensorId);
+            int userId, @NonNull String owner, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
+        super(context, lazyDaemon, token, userId, owner, sensorId, logger, biometricContext);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
index cb9c33e..a4e6025 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.os.Build;
 import android.os.Environment;
@@ -27,6 +26,8 @@
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.HalClientMonitor;
 
@@ -50,12 +51,13 @@
 
     FingerprintUpdateActiveUserClient(@NonNull Context context,
             @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, int userId,
-            @NonNull String owner, int sensorId, Supplier<Integer> currentUserId,
+            @NonNull String owner, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            Supplier<Integer> currentUserId,
             boolean hasEnrolledBiometrics, @NonNull Map<Integer, Long> authenticatorIds,
             boolean forceUpdateAuthenticatorId) {
         super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
-                0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
-                BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+                0 /* cookie */, sensorId, logger, biometricContext);
         mCurrentUserId = currentUserId;
         mForceUpdateAuthenticatorId = forceUpdateAuthenticatorId;
         mHasEnrolledBiometrics = hasEnrolledBiometrics;
diff --git a/services/core/java/com/android/server/display/BrightnessUtils.java b/services/core/java/com/android/server/display/BrightnessUtils.java
new file mode 100644
index 0000000..84fa0cc
--- /dev/null
+++ b/services/core/java/com/android/server/display/BrightnessUtils.java
@@ -0,0 +1,83 @@
+/*
+ * 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.display;
+
+import android.util.MathUtils;
+
+/**
+ * Utility class providing functions to convert between linear and perceptual gamma space.
+ *
+ * Internally, this implements the Hybrid Log Gamma electro-optical transfer function, which is a
+ * slight improvement to the typical gamma transfer function for displays whose max brightness
+ * exceeds the 120 nit reference point, but doesn't set a specific reference brightness like the PQ
+ * function does.
+ *
+ * Note that this transfer function is only valid if the display's backlight value is a linear
+ * control. If it's calibrated to be something non-linear, then a different transfer function
+ * should be used.
+ *
+ * Note: This code is based on the same class in the com.android.settingslib.display package.
+ */
+public class BrightnessUtils {
+
+    // Hybrid Log Gamma constant values
+    private static final float R = 0.5f;
+    private static final float A = 0.17883277f;
+    private static final float B = 0.28466892f;
+    private static final float C = 0.55991073f;
+
+    /**
+     * A function for converting from the gamma space into the linear space.
+     *
+     * @param val The value in the gamma space [0 .. 1.0]
+     * @return The corresponding value in the linear space [0 .. 1.0].
+     */
+    public static final float convertGammaToLinear(float val) {
+        final float ret;
+        if (val <= R) {
+            ret = MathUtils.sq(val / R);
+        } else {
+            ret = MathUtils.exp((val - C) / A) + B;
+        }
+
+        // HLG is normalized to the range [0, 12], ensure that value is within that range,
+        // it shouldn't be out of bounds.
+        final float normalizedRet = MathUtils.constrain(ret, 0, 12);
+
+        // Re-normalize to the range [0, 1]
+        // in order to derive the correct setting value.
+        return normalizedRet / 12;
+    }
+
+    /**
+     * A function for converting from the linear space into the gamma space.
+     *
+     * @param val The value in linear space [0 .. 1.0]
+     * @return The corresponding value in gamma space [0 .. 1.0]
+     */
+    public static final float convertLinearToGamma(float val) {
+        // For some reason, HLG normalizes to the range [0, 12] rather than [0, 1]
+        final float normalizedVal = val * 12;
+        final float ret;
+        if (normalizedVal <= 1f) {
+            ret = MathUtils.sqrt(normalizedVal) * R;
+        } else {
+            ret = A * MathUtils.log(normalizedVal - B) + C;
+        }
+        return ret;
+    }
+}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 7ad4979..4e88acd 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -141,7 +141,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.CopyOnWriteArrayList;
@@ -1756,10 +1755,6 @@
 
     void setUserPreferredDisplayModeInternal(int displayId, Display.Mode mode) {
         synchronized (mSyncRoot) {
-            if (Objects.equals(mUserPreferredMode, mode) && displayId == Display.INVALID_DISPLAY) {
-                return;
-            }
-
             if (mode != null && !isResolutionAndRefreshRateValid(mode)
                     && displayId == Display.INVALID_DISPLAY) {
                 throw new IllegalArgumentException("width, height and refresh rate of mode should "
@@ -1813,7 +1808,15 @@
         Settings.Global.putInt(mContext.getContentResolver(),
                 Settings.Global.USER_PREFERRED_RESOLUTION_WIDTH, resolutionWidth);
         mDisplayDeviceRepo.forEachLocked((DisplayDevice device) -> {
-            device.setUserPreferredDisplayModeLocked(mode);
+            // If there is a display specific mode, don't override that
+            final Point deviceUserPreferredResolution =
+                    mPersistentDataStore.getUserPreferredResolution(device);
+            final float deviceRefreshRate =
+                    mPersistentDataStore.getUserPreferredRefreshRate(device);
+            if (!isValidResolution(deviceUserPreferredResolution)
+                    && !isValidRefreshRate(deviceRefreshRate)) {
+                device.setUserPreferredDisplayModeLocked(mode);
+            }
         });
     }
 
@@ -3533,6 +3536,14 @@
                 && (brightness <= PowerManager.BRIGHTNESS_MAX);
     }
 
+    private static boolean isValidResolution(Point resolution) {
+        return (resolution != null) && (resolution.x > 0) && (resolution.y > 0);
+    }
+
+    private static boolean isValidRefreshRate(float refreshRate) {
+        return !Float.isNaN(refreshRate) && (refreshRate > 0.0f);
+    }
+
     private final class LocalService extends DisplayManagerInternal {
 
         @Override
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index a9a1f08..a9875c8 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -335,7 +335,7 @@
     }
 
     private int setUserDisabledHdrTypes() {
-        final String[] userDisabledHdrTypesText = getAllArgs();
+        String[] userDisabledHdrTypesText = peekRemainingArgs();
         if (userDisabledHdrTypesText == null) {
             getErrPrintWriter().println("Error: no userDisabledHdrTypes specified");
             return 1;
@@ -351,7 +351,6 @@
             getErrPrintWriter().println("Error: invalid format of userDisabledHdrTypes");
             return 1;
         }
-
         final Context context = mService.getContext();
         final DisplayManager dm = context.getSystemService(DisplayManager.class);
         dm.setUserDisabledHdrTypes(userDisabledHdrTypes);
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java
index 534ed5d..23c17f5 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java
@@ -521,6 +521,10 @@
             } else if (mIsBlockedByLowPowerMode) {
                 reason = FrameworkStatsLog
                                  .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_BATTERY_SAVE_ON;
+            } else if (mBrightness <= mHbmData.transitionPoint) {
+                // This must be after external thermal check.
+                reason = FrameworkStatsLog
+                            .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LOW_REQUESTED_BRIGHTNESS;
             }
         }
 
diff --git a/services/core/java/com/android/server/display/RampAnimator.java b/services/core/java/com/android/server/display/RampAnimator.java
index ed3b15f..1ebd1f5a 100644
--- a/services/core/java/com/android/server/display/RampAnimator.java
+++ b/services/core/java/com/android/server/display/RampAnimator.java
@@ -23,6 +23,8 @@
 /**
  * A custom animator that progressively updates a property value at
  * a given variable rate until it reaches a particular target value.
+ * The ramping at the given rate is done in the perceptual space using
+ * the HLG transfer functions.
  */
 class RampAnimator<T> {
     private final T mObject;
@@ -57,7 +59,9 @@
      * @param rate The convergence rate in units per second, or 0 to set the value immediately.
      * @return True if the target differs from the previous target.
      */
-    public boolean animateTo(float target, float rate) {
+    public boolean animateTo(float targetLinear, float rate) {
+        // Convert the target from the linear into the HLG space.
+        final float target = BrightnessUtils.convertLinearToGamma(targetLinear);
 
         // Immediately jump to the target the first time.
         if (mFirstTime || rate <= 0) {
@@ -156,7 +160,9 @@
             final float oldCurrentValue = mCurrentValue;
             mCurrentValue = mAnimatedValue;
             if (oldCurrentValue != mCurrentValue) {
-                mProperty.setValue(mObject, mCurrentValue);
+                // Convert value from HLG into linear space for the property.
+                final float linearCurrentVal = BrightnessUtils.convertGammaToLinear(mCurrentValue);
+                mProperty.setValue(mObject, linearCurrentVal);
             }
             if (mTargetValue != mCurrentValue) {
                 postAnimationCallback();
@@ -201,14 +207,14 @@
          * If this is the first time the property is being set or if the rate is 0,
          * the value jumps directly to the target.
          *
-         * @param firstTarget The first target value.
-         * @param secondTarget The second target value.
+         * @param linearFirstTarget The first target value in linear space.
+         * @param linearSecondTarget The second target value in linear space.
          * @param rate The convergence rate in units per second, or 0 to set the value immediately.
          * @return True if either target differs from the previous target.
          */
-        public boolean animateTo(float firstTarget, float secondTarget, float rate) {
-            final boolean firstRetval = mFirst.animateTo(firstTarget, rate);
-            final boolean secondRetval = mSecond.animateTo(secondTarget, rate);
+        public boolean animateTo(float linearFirstTarget, float linearSecondTarget, float rate) {
+            final boolean firstRetval = mFirst.animateTo(linearFirstTarget, rate);
+            final boolean secondRetval = mSecond.animateTo(linearSecondTarget, rate);
             return firstRetval && secondRetval;
         }
 
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index de933cc..783a88c 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -2285,14 +2285,8 @@
         nativeNotifyPortAssociationsChanged(mPtr);
     }
 
-    /**
-     * Add a runtime association between the input device name and the display unique id.
-     * @param inputDeviceName The name of the input device.
-     * @param displayUniqueId The unique id of the associated display.
-     */
     @Override // Binder call
-    public void addUniqueIdAssociation(@NonNull String inputDeviceName,
-            @NonNull String displayUniqueId) {
+    public void addUniqueIdAssociation(@NonNull String inputPort, @NonNull String displayUniqueId) {
         if (!checkCallingPermission(
                 android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY,
                 "addNameAssociation()")) {
@@ -2300,20 +2294,16 @@
                     "Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY permission");
         }
 
-        Objects.requireNonNull(inputDeviceName);
+        Objects.requireNonNull(inputPort);
         Objects.requireNonNull(displayUniqueId);
         synchronized (mAssociationsLock) {
-            mUniqueIdAssociations.put(inputDeviceName, displayUniqueId);
+            mUniqueIdAssociations.put(inputPort, displayUniqueId);
         }
         nativeChangeUniqueIdAssociation(mPtr);
     }
 
-    /**
-     * Remove the runtime association between the input device and the display.
-     * @param inputDeviceName The port of the input device to be cleared.
-     */
     @Override // Binder call
-    public void removeUniqueIdAssociation(@NonNull String inputDeviceName) {
+    public void removeUniqueIdAssociation(@NonNull String inputPort) {
         if (!checkCallingPermission(
                 android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY,
                 "removeUniqueIdAssociation()")) {
@@ -2321,9 +2311,9 @@
                     "Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY permission");
         }
 
-        Objects.requireNonNull(inputDeviceName);
+        Objects.requireNonNull(inputPort);
         synchronized (mAssociationsLock) {
-            mUniqueIdAssociations.remove(inputDeviceName);
+            mUniqueIdAssociations.remove(inputPort);
         }
         nativeChangeUniqueIdAssociation(mPtr);
     }
@@ -2594,6 +2584,13 @@
                     pw.println("  display: " + v);
                 });
             }
+            if (!mUniqueIdAssociations.isEmpty()) {
+                pw.println("Unique Id Associations:");
+                mUniqueIdAssociations.forEach((k, v) -> {
+                    pw.print("  port: " + k);
+                    pw.println("  uniqueId: " + v);
+                });
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
index c87ca92..6cb3b3b 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
@@ -107,9 +107,11 @@
 
     @AnyThread
     void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps,
-            int configChanges, boolean stylusHwSupported) {
+            int configChanges, boolean stylusHwSupported,
+            boolean shouldShowImeSwitcherWhenImeIsShown) {
         try {
-            mTarget.initializeInternal(token, privOps, configChanges, stylusHwSupported);
+            mTarget.initializeInternal(token, privOps, configChanges, stylusHwSupported,
+                    shouldShowImeSwitcherWhenImeIsShown);
         } catch (RemoteException e) {
             logRemoteException(e);
         }
@@ -145,9 +147,20 @@
 
     @AnyThread
     void startInput(IBinder startInputToken, IInputContext inputContext, EditorInfo attribute,
-            boolean restarting) {
+            boolean restarting, boolean shouldShowImeSwitcherWhenImeIsShown) {
         try {
-            mTarget.startInput(startInputToken, inputContext, attribute, restarting);
+            mTarget.startInput(startInputToken, inputContext, attribute, restarting,
+                    shouldShowImeSwitcherWhenImeIsShown);
+        } catch (RemoteException e) {
+            logRemoteException(e);
+        }
+    }
+
+    @AnyThread
+    void onShouldShowImeSwitcherWhenImeIsShownChanged(boolean shouldShowImeSwitcherWhenImeIsShown) {
+        try {
+            mTarget.onShouldShowImeSwitcherWhenImeIsShownChanged(
+                    shouldShowImeSwitcherWhenImeIsShown);
         } catch (RemoteException e) {
             logRemoteException(e);
         }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 936b1a2..c207738a 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2325,10 +2325,12 @@
                     true /* direct */);
         }
 
+        final boolean shouldShowImeSwitcherWhenImeIsShown =
+                shouldShowImeSwitcherWhenImeIsShownLocked();
         final SessionState session = mCurClient.curSession;
         setEnabledSessionLocked(session);
-        session.method.startInput(startInputToken, mCurInputContext, mCurAttribute, restarting);
-
+        session.method.startInput(startInputToken, mCurInputContext, mCurAttribute, restarting,
+                shouldShowImeSwitcherWhenImeIsShown);
         if (mShowRequested) {
             if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
             showCurrentInputLocked(mCurFocusedWindow, getAppShowFlagsLocked(), null,
@@ -2528,7 +2530,7 @@
                     + mCurTokenDisplayId);
         }
         inputMethod.initializeInternal(token, new InputMethodPrivilegedOperationsImpl(this, token),
-                configChanges, supportStylusHw);
+                configChanges, supportStylusHw, shouldShowImeSwitcherWhenImeIsShownLocked());
     }
 
     @AnyThread
@@ -2731,6 +2733,12 @@
     }
 
     @GuardedBy("ImfLock.class")
+    boolean shouldShowImeSwitcherWhenImeIsShownLocked() {
+        return shouldShowImeSwitcherLocked(
+                InputMethodService.IME_ACTIVE | InputMethodService.IME_VISIBLE);
+    }
+
+    @GuardedBy("ImfLock.class")
     private boolean shouldShowImeSwitcherLocked(int visibility) {
         if (!mShowOngoingImeSwitcherForPhones) return false;
         if (mMenuController.getSwitchingDialogLocked() != null) return false;
@@ -2990,6 +2998,7 @@
         // the same enabled IMEs list.
         mSwitchingController.resetCircularListLocked(mContext);
 
+        sendShouldShowImeSwitcherWhenImeIsShownLocked();
     }
 
     @GuardedBy("ImfLock.class")
@@ -4308,6 +4317,7 @@
                 updateImeWindowStatus(msg.arg1 == 1);
                 return true;
             }
+
             // ---------------------------------------------------------
 
             case MSG_UNBIND_CLIENT:
@@ -4368,6 +4378,9 @@
             // --------------------------------------------------------------
             case MSG_HARD_KEYBOARD_SWITCH_CHANGED:
                 mMenuController.handleHardKeyboardStatusChange(msg.arg1 == 1);
+                synchronized (ImfLock.class) {
+                    sendShouldShowImeSwitcherWhenImeIsShownLocked();
+                }
                 return true;
             case MSG_SYSTEM_UNLOCK_USER: {
                 final int userId = msg.arg1;
@@ -4638,6 +4651,8 @@
         // the same enabled IMEs list.
         mSwitchingController.resetCircularListLocked(mContext);
 
+        sendShouldShowImeSwitcherWhenImeIsShownLocked();
+
         // Notify InputMethodListListeners of the new installed InputMethods.
         final List<InputMethodInfo> inputMethodList = new ArrayList<>(mMethodList);
         mHandler.obtainMessage(MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED,
@@ -4645,6 +4660,17 @@
     }
 
     @GuardedBy("ImfLock.class")
+    void sendShouldShowImeSwitcherWhenImeIsShownLocked() {
+        final IInputMethodInvoker curMethod = mBindingController.getCurMethod();
+        if (curMethod == null) {
+            // No need to send the data if the IME is not yet bound.
+            return;
+        }
+        curMethod.onShouldShowImeSwitcherWhenImeIsShownChanged(
+                shouldShowImeSwitcherWhenImeIsShownLocked());
+    }
+
+    @GuardedBy("ImfLock.class")
     private void updateDefaultVoiceImeIfNeededLocked() {
         final String systemSpeechRecognizer =
                 mContext.getString(com.android.internal.R.string.config_systemSpeechRecognizer);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index 348bb2d..98bde11 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -203,6 +203,7 @@
             attrs.setTitle("Select input method");
             w.setAttributes(attrs);
             mService.updateSystemUiLocked();
+            mService.sendShouldShowImeSwitcherWhenImeIsShownLocked();
             mSwitchingDialog.show();
         }
     }
@@ -238,6 +239,7 @@
             mSwitchingDialogTitleView = null;
 
             mService.updateSystemUiLocked();
+            mService.sendShouldShowImeSwitcherWhenImeIsShownLocked();
             mDialogBuilder = null;
             mIms = null;
         }
diff --git a/services/core/java/com/android/server/location/LocationShellCommand.java b/services/core/java/com/android/server/location/LocationShellCommand.java
index b65338d..9c85d18 100644
--- a/services/core/java/com/android/server/location/LocationShellCommand.java
+++ b/services/core/java/com/android/server/location/LocationShellCommand.java
@@ -69,6 +69,14 @@
                 handleSetAdasGnssLocationEnabled();
                 return 0;
             }
+            case "set-automotive-gnss-suspended": {
+                handleSetAutomotiveGnssSuspended();
+                return 0;
+            }
+            case "is-automotive-gnss-suspended": {
+                handleIsAutomotiveGnssSuspended();
+                return 0;
+            }
             case "providers": {
                 String command = getNextArgRequired();
                 return parseProvidersCommand(command);
@@ -189,6 +197,24 @@
         mService.setAdasGnssLocationEnabledForUser(enabled, userId);
     }
 
+    private void handleSetAutomotiveGnssSuspended() {
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+            throw new IllegalStateException("command only recognized on automotive devices");
+        }
+
+        boolean suspended = Boolean.parseBoolean(getNextArgRequired());
+
+        mService.setAutomotiveGnssSuspended(suspended);
+    }
+
+    private void handleIsAutomotiveGnssSuspended() {
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+            throw new IllegalStateException("command only recognized on automotive devices");
+        }
+
+        getOutPrintWriter().println(mService.isAutomotiveGnssSuspended());
+    }
+
     private void handleAddTestProvider() {
         String provider = getNextArgRequired();
 
@@ -359,6 +385,10 @@
             pw.println("  set-adas-gnss-location-enabled true|false [--user <USER_ID>]");
             pw.println("    Sets the ADAS GNSS location enabled state. If no user is specified,");
             pw.println("    the current user is assumed.");
+            pw.println("  is-automotive-gnss-suspended");
+            pw.println("    Gets the automotive GNSS suspended state.");
+            pw.println("  set-automotive-gnss-suspended true|false");
+            pw.println("    Sets the automotive GNSS suspended state.");
         }
         pw.println("  providers");
         pw.println("    The providers command is followed by a subcommand, as listed below:");
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index fac7a40..8ce67a6 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -98,8 +98,6 @@
 import static android.net.NetworkTemplate.MATCH_CARRIER;
 import static android.net.NetworkTemplate.MATCH_MOBILE;
 import static android.net.NetworkTemplate.MATCH_WIFI;
-import static android.net.NetworkTemplate.buildTemplateCarrierMetered;
-import static android.net.NetworkTemplate.buildTemplateMobileAll;
 import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED;
 import static android.os.Trace.TRACE_TAG_NETWORK;
 import static android.provider.Settings.Global.NETPOLICY_OVERRIDE_ENABLED;
@@ -168,7 +166,6 @@
 import android.net.INetworkManagementEventObserver;
 import android.net.INetworkPolicyListener;
 import android.net.INetworkPolicyManager;
-import android.net.INetworkStatsService;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkIdentity;
@@ -1324,7 +1321,7 @@
     };
 
     /**
-     * Check {@link NetworkPolicy} against current {@link INetworkStatsService}
+     * Check {@link NetworkPolicy} against current {@link NetworkStatsManager}
      * to show visible notifications as needed.
      */
     @GuardedBy("mNetworkPoliciesSecondLock")
@@ -2322,6 +2319,18 @@
     }
 
     /**
+     * Template to match all metered carrier networks with the given IMSI.
+     *
+     * @hide
+     */
+    public static NetworkTemplate buildTemplateCarrierMetered(@NonNull String subscriberId) {
+        Objects.requireNonNull(subscriberId);
+        return new NetworkTemplate.Builder(MATCH_CARRIER)
+                .setSubscriberIds(Set.of(subscriberId))
+                .setMeteredness(METERED_YES).build();
+    }
+
+    /**
      * Update the given {@link NetworkPolicy} based on any carrier-provided
      * defaults via {@link SubscriptionPlan} or {@link CarrierConfigManager}.
      * Leaves policy untouched if the user has modified it.
@@ -2724,7 +2733,8 @@
 
                 out.startTag(null, TAG_NETWORK_POLICY);
                 writeIntAttribute(out, ATTR_NETWORK_TEMPLATE, template.getMatchRule());
-                final String subscriberId = template.getSubscriberId();
+                final String subscriberId = template.getSubscriberIds().isEmpty() ? null
+                        : template.getSubscriberIds().iterator().next();
                 if (subscriberId != null) {
                     out.attribute(null, ATTR_SUBSCRIBER_ID, subscriberId);
                 }
@@ -3101,7 +3111,7 @@
             }
             // When two normalized templates conflict, prefer the most
             // restrictive policy
-            policy.template = NetworkTemplate.normalize(policy.template, mMergedSubscriberIds);
+            policy.template = normalizeTemplate(policy.template, mMergedSubscriberIds);
             final NetworkPolicy existing = mNetworkPolicy.get(policy.template);
             if (existing == null || existing.compareTo(policy) > 0) {
                 if (existing != null) {
@@ -3112,6 +3122,46 @@
         }
     }
 
+    /**
+     * Examine the given template and normalize it.
+     * We pick the "lowest" merged subscriber as the primary
+     * for key purposes, and expand the template to match all other merged
+     * subscribers.
+     *
+     * There can be multiple merged subscriberIds for multi-SIM devices.
+     *
+     * <p>
+     * For example, given an incoming template matching B, and the currently
+     * active merge set [A,B], we'd return a new template that primarily matches
+     * A, but also matches B.
+     */
+    private static NetworkTemplate normalizeTemplate(@NonNull NetworkTemplate template,
+            @NonNull List<String[]> mergedList) {
+        // Now there are several types of network which uses Subscriber Id to store network
+        // information. For instance:
+        // 1. A merged carrier wifi network which has TYPE_WIFI with a Subscriber Id.
+        // 2. A typical cellular network could have TYPE_MOBILE with a Subscriber Id.
+
+        if (template.getSubscriberIds().isEmpty()) return template;
+
+        for (final String[] merged : mergedList) {
+            // TODO: Handle incompatible subscriberIds if that happens in practice.
+            for (final String subscriberId : template.getSubscriberIds()) {
+                if (com.android.net.module.util.CollectionUtils.contains(merged, subscriberId)) {
+                    // Requested template subscriber is part of the merged group; return
+                    // a template that matches all merged subscribers.
+                    return new NetworkTemplate.Builder(template.getMatchRule())
+                            .setWifiNetworkKeys(template.getWifiNetworkKeys())
+                            .setSubscriberIds(Set.of(merged))
+                            .setMeteredness(template.getMeteredness())
+                            .build();
+                }
+            }
+        }
+
+        return template;
+    }
+
     @Override
     public void snoozeLimit(NetworkTemplate template) {
         mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
@@ -5559,7 +5609,11 @@
         NetworkPolicy[] policies = getNetworkPolicies(mContext.getOpPackageName());
         NetworkTemplate templateCarrier = subscriber != null
                 ? buildTemplateCarrierMetered(subscriber) : null;
-        NetworkTemplate templateMobile = buildTemplateMobileAll(subscriber);
+        NetworkTemplate templateMobile = subscriber != null
+                ? new NetworkTemplate.Builder(MATCH_MOBILE)
+                .setSubscriberIds(Set.of(subscriber))
+                .setMeteredness(android.net.NetworkStats.METERED_YES)
+                .build() : null;
         for (NetworkPolicy policy : policies) {
             //  All policies loaded from disk will be carrier templates, and setting will also only
             //  set carrier templates, but we clear mobile templates just in case one is set by
diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java
index 583cdd5..647a89e 100644
--- a/services/core/java/com/android/server/notification/NotificationComparator.java
+++ b/services/core/java/com/android/server/notification/NotificationComparator.java
@@ -104,6 +104,12 @@
             return -1 * Boolean.compare(leftPeople, rightPeople);
         }
 
+        boolean leftSystemMax = isSystemMax(left);
+        boolean rightSystemMax = isSystemMax(right);
+        if (leftSystemMax != rightSystemMax) {
+            return -1 * Boolean.compare(leftSystemMax, rightSystemMax);
+        }
+
         if (leftImportance != rightImportance) {
             // by importance, high to low
             return -1 * Integer.compare(leftImportance, rightImportance);
@@ -173,6 +179,20 @@
         return mMessagingUtil.isImportantMessaging(record.getSbn(), record.getImportance());
     }
 
+    protected boolean isSystemMax(NotificationRecord record) {
+        if (record.getImportance() < NotificationManager.IMPORTANCE_HIGH) {
+            return false;
+        }
+        String packageName = record.getSbn().getPackageName();
+        if ("android".equals(packageName)) {
+            return true;
+        }
+        if ("com.android.systemui".equals(packageName)) {
+            return true;
+        }
+        return false;
+    }
+
     private boolean isOngoing(NotificationRecord record) {
         final int ongoingFlags = Notification.FLAG_FOREGROUND_SERVICE;
         return (record.getNotification().flags & ongoingFlags) != 0;
diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java
index 0cbdbc1..5d18069 100644
--- a/services/core/java/com/android/server/notification/PermissionHelper.java
+++ b/services/core/java/com/android/server/notification/PermissionHelper.java
@@ -19,7 +19,7 @@
 import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
 import static android.content.pm.PackageManager.GET_PERMISSIONS;
-import static android.permission.PermissionManager.PERMISSION_GRANTED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -77,7 +77,8 @@
         assertFlag();
         final long callingId = Binder.clearCallingIdentity();
         try {
-            return mPmi.checkUidPermission(uid, NOTIFICATION_PERMISSION) == PERMISSION_GRANTED;
+            return mPmi.checkPostNotificationsPermissionGrantedOrLegacyAccess(uid)
+                    == PERMISSION_GRANTED;
         } finally {
             Binder.restoreCallingIdentity(callingId);
         }
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 1fa9013..9bcb724 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -285,6 +285,19 @@
     );
 
     /**
+     * User restrictions available to a device owner whose type is
+     * {@link android.app.admin.DevicePolicyManager#DEVICE_OWNER_TYPE_FINANCED}.
+     */
+    private static final Set<String> FINANCED_DEVICE_OWNER_RESTRICTIONS = Sets.newArraySet(
+            UserManager.DISALLOW_ADD_USER,
+            UserManager.DISALLOW_DEBUGGING_FEATURES,
+            UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
+            UserManager.DISALLOW_SAFE_BOOT,
+            UserManager.DISALLOW_CONFIG_DATE_TIME,
+            UserManager.DISALLOW_OUTGOING_CALLS
+    );
+
+    /**
      * Returns whether the given restriction name is valid (and logs it if it isn't).
      */
     public static boolean isValidRestriction(@NonNull String restriction) {
@@ -458,6 +471,15 @@
     }
 
     /**
+     * @return {@code true} only if the restriction is allowed for financed devices and can be set
+     * by a device owner. Otherwise, {@code false} would be returned.
+     */
+    public static boolean canFinancedDeviceOwnerChange(String restriction) {
+        return FINANCED_DEVICE_OWNER_RESTRICTIONS.contains(restriction)
+                && canDeviceOwnerChange(restriction);
+    }
+
+    /**
      * Whether given user restriction should be enforced globally.
      */
     public static boolean isGlobal(@UserManagerInternal.OwnerType int restrictionOwnerType,
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 317730a..79c5ea2 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -17,6 +17,7 @@
 package com.android.server.pm.permission;
 
 import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD;
+import static android.Manifest.permission.POST_NOTIFICATIONS;
 import static android.Manifest.permission.RECORD_AUDIO;
 import static android.Manifest.permission.UPDATE_APP_OPS_STATS;
 import static android.app.AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE;
@@ -608,6 +609,21 @@
         }
 
         @Override
+        public int checkPostNotificationsPermissionGrantedOrLegacyAccess(int uid) {
+            int granted = PermissionManagerService.this.checkUidPermission(uid,
+                    POST_NOTIFICATIONS);
+            AndroidPackage pkg = mPackageManagerInt.getPackage(uid);
+            if (granted != PermissionManager.PERMISSION_GRANTED) {
+                int flags = PermissionManagerService.this.getPermissionFlags(pkg.getPackageName(),
+                        POST_NOTIFICATIONS, UserHandle.getUserId(uid));
+                if ((flags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
+                    return PermissionManager.PERMISSION_GRANTED;
+                }
+            }
+            return granted;
+        }
+
+        @Override
         public void startShellPermissionIdentityDelegation(int uid, @NonNull String packageName,
                 @Nullable List<String> permissionNames) {
             Objects.requireNonNull(packageName, "packageName");
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
index d2c4ec4..812d7a0 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -63,6 +63,17 @@
     int checkUidPermission(int uid, @NonNull String permissionName);
 
     /**
+     * Check whether a particular UID has been granted the POST_NOTIFICATIONS permission, or if
+     * access should be granted based on legacy access (currently symbolized by the REVIEW_REQUIRED
+     * permission flag
+     *
+     * @param uid the UID
+     * @return {@code PERMISSION_GRANTED} if the permission is granted, or legacy access is granted,
+     *         {@code PERMISSION_DENIED} otherwise
+     */
+    int checkPostNotificationsPermissionGrantedOrLegacyAccess(int uid);
+
+    /**
      * Adds a listener for runtime permission state (permissions or flags) changes.
      *
      * @param listener The listener.
diff --git a/services/core/java/com/android/server/power/LowPowerStandbyController.java b/services/core/java/com/android/server/power/LowPowerStandbyController.java
new file mode 100644
index 0000000..cea84b5
--- /dev/null
+++ b/services/core/java/com/android/server/power/LowPowerStandbyController.java
@@ -0,0 +1,608 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power;
+
+import android.app.AlarmManager;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.PowerManagerInternal;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+import android.util.SparseBooleanArray;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+
+/**
+ * Controls Low Power Standby state.
+ *
+ * Instantiated by {@link PowerManagerService} only if Low Power Standby is supported.
+ *
+ * <p>Low Power Standby is active when all of the following conditions are met:
+ * <ul>
+ *   <li>Low Power Standby is enabled
+ *   <li>The device is not interactive, and has been non-interactive for a given timeout
+ *   <li>The device is not in a doze maintenance window
+ * </ul>
+ *
+ * <p>When Low Power Standby is active, the following restrictions are applied to applications
+ * with procstate less important than {@link android.app.ActivityManager#PROCESS_STATE_BOUND_TOP}:
+ * <ul>
+ *   <li>Network access is blocked
+ *   <li>Wakelocks are disabled
+ * </ul>
+ *
+ * @hide
+ */
+public final class LowPowerStandbyController {
+    private static final String TAG = "LowPowerStandbyController";
+    private static final boolean DEBUG = false;
+    private static final boolean DEFAULT_ACTIVE_DURING_MAINTENANCE = false;
+
+    private static final int MSG_STANDBY_TIMEOUT = 0;
+    private static final int MSG_NOTIFY_ACTIVE_CHANGED = 1;
+    private static final int MSG_NOTIFY_ALLOWLIST_CHANGED = 2;
+
+    private final Handler mHandler;
+    private final SettingsObserver mSettingsObserver;
+    private final Object mLock = new Object();
+
+    private final Context mContext;
+    private final Clock mClock;
+    private final AlarmManager.OnAlarmListener mOnStandbyTimeoutExpired =
+            this::onStandbyTimeoutExpired;
+    private final LowPowerStandbyControllerInternal mLocalService = new LocalService();
+    private final SparseBooleanArray mAllowlistUids = new SparseBooleanArray();
+
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            switch (intent.getAction()) {
+                case Intent.ACTION_SCREEN_OFF:
+                    onNonInteractive();
+                    break;
+                case Intent.ACTION_SCREEN_ON:
+                    onInteractive();
+                    break;
+                case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED:
+                    onDeviceIdleModeChanged();
+                    break;
+            }
+        }
+    };
+
+    @GuardedBy("mLock")
+    private AlarmManager mAlarmManager;
+    @GuardedBy("mLock")
+    private PowerManager mPowerManager;
+    @GuardedBy("mLock")
+    private boolean mSupportedConfig;
+    @GuardedBy("mLock")
+    private boolean mEnabledByDefaultConfig;
+    @GuardedBy("mLock")
+    private int mStandbyTimeoutConfig;
+
+    /** Whether Low Power Standby is enabled in Settings */
+    @GuardedBy("mLock")
+    private boolean mIsEnabled;
+
+    /**
+     * Whether Low Power Standby is currently active (enforcing restrictions).
+     */
+    @GuardedBy("mLock")
+    private boolean mIsActive;
+
+    /** Whether the device is currently interactive */
+    @GuardedBy("mLock")
+    private boolean mIsInteractive;
+
+    /** The time the device was last interactive, in {@link SystemClock#elapsedRealtime()}. */
+    @GuardedBy("mLock")
+    private long mLastInteractiveTimeElapsed;
+
+    /**
+     * Whether we are in device idle mode.
+     * During maintenance windows Low Power Standby is deactivated to allow
+     * apps to run maintenance tasks.
+     */
+    @GuardedBy("mLock")
+    private boolean mIsDeviceIdle;
+
+    /**
+     * Whether the device has entered idle mode since becoming non-interactive.
+     * In the initial non-idle period after turning the screen off, Low Power Standby is already
+     * allowed to become active. Later non-idle periods are treated as maintenance windows, during
+     * which Low Power Standby is deactivated to allow apps to run maintenance tasks.
+     */
+    @GuardedBy("mLock")
+    private boolean mIdleSinceNonInteractive;
+
+    /** Whether Low Power Standby restrictions should be active during doze maintenance mode. */
+    @GuardedBy("mLock")
+    private boolean mActiveDuringMaintenance;
+
+    /** Force Low Power Standby to be active. */
+    @GuardedBy("mLock")
+    private boolean mForceActive;
+
+    /** Functional interface for providing time. */
+    @VisibleForTesting
+    interface Clock {
+        /** Returns milliseconds since boot, including time spent in sleep. */
+        long elapsedRealtime();
+    }
+
+    public LowPowerStandbyController(Context context, Looper looper, Clock clock) {
+        mContext = context;
+        mHandler = new LowPowerStandbyHandler(looper);
+        mClock = clock;
+        mSettingsObserver = new SettingsObserver(mHandler);
+    }
+
+    void systemReady() {
+        final Resources resources = mContext.getResources();
+        synchronized (mLock) {
+            mSupportedConfig = resources.getBoolean(
+                    com.android.internal.R.bool.config_lowPowerStandbySupported);
+
+            if (!mSupportedConfig) {
+                return;
+            }
+
+            mAlarmManager = mContext.getSystemService(AlarmManager.class);
+            mPowerManager = mContext.getSystemService(PowerManager.class);
+
+            mStandbyTimeoutConfig = resources.getInteger(
+                    R.integer.config_lowPowerStandbyNonInteractiveTimeout);
+            mEnabledByDefaultConfig = resources.getBoolean(
+                    R.bool.config_lowPowerStandbyEnabledByDefault);
+
+            mIsInteractive = mPowerManager.isInteractive();
+
+            mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor(
+                    Settings.Global.LOW_POWER_STANDBY_ENABLED),
+                    false, mSettingsObserver, UserHandle.USER_ALL);
+            mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor(
+                    Settings.Global.LOW_POWER_STANDBY_ACTIVE_DURING_MAINTENANCE),
+                    false, mSettingsObserver, UserHandle.USER_ALL);
+            updateSettingsLocked();
+
+            if (mIsEnabled) {
+                registerBroadcastReceiver();
+            }
+        }
+
+        LocalServices.addService(LowPowerStandbyControllerInternal.class, mLocalService);
+    }
+
+    @GuardedBy("mLock")
+    private void updateSettingsLocked() {
+        final ContentResolver resolver = mContext.getContentResolver();
+        mIsEnabled = mSupportedConfig && Settings.Global.getInt(resolver,
+                Settings.Global.LOW_POWER_STANDBY_ENABLED,
+                mEnabledByDefaultConfig ? 1 : 0) != 0;
+        mActiveDuringMaintenance = Settings.Global.getInt(resolver,
+                Settings.Global.LOW_POWER_STANDBY_ACTIVE_DURING_MAINTENANCE,
+                DEFAULT_ACTIVE_DURING_MAINTENANCE ? 1 : 0) != 0;
+
+        updateActiveLocked();
+    }
+
+    @GuardedBy("mLock")
+    private void updateActiveLocked() {
+        final long now = mClock.elapsedRealtime();
+        final boolean standbyTimeoutExpired =
+                (now - mLastInteractiveTimeElapsed) >= mStandbyTimeoutConfig;
+        final boolean maintenanceMode = mIdleSinceNonInteractive && !mIsDeviceIdle;
+        final boolean newActive =
+                mForceActive || (mIsEnabled && !mIsInteractive && standbyTimeoutExpired
+                        && (!maintenanceMode || mActiveDuringMaintenance));
+        if (DEBUG) {
+            Slog.d(TAG, "updateActiveLocked: mIsEnabled=" + mIsEnabled + ", mIsInteractive="
+                    + mIsInteractive + ", standbyTimeoutExpired=" + standbyTimeoutExpired
+                    + ", mIdleSinceNonInteractive=" + mIdleSinceNonInteractive + ", mIsDeviceIdle="
+                    + mIsDeviceIdle + ", mActiveDuringMaintenance=" + mActiveDuringMaintenance
+                    + ", mForceActive=" + mForceActive + ", mIsActive=" + mIsActive + ", newActive="
+                    + newActive);
+        }
+        if (mIsActive != newActive) {
+            mIsActive = newActive;
+            if (DEBUG) {
+                Slog.d(TAG, "mIsActive changed, mIsActive=" + mIsActive);
+            }
+            enqueueNotifyActiveChangedLocked();
+        }
+    }
+
+    private void onNonInteractive() {
+        if (DEBUG) {
+            Slog.d(TAG, "onNonInteractive");
+        }
+        final long now = mClock.elapsedRealtime();
+        synchronized (mLock) {
+            mIsInteractive = false;
+            mIsDeviceIdle = false;
+            mLastInteractiveTimeElapsed = now;
+
+            if (mStandbyTimeoutConfig > 0) {
+                scheduleStandbyTimeoutAlarmLocked();
+            }
+
+            updateActiveLocked();
+        }
+    }
+
+    private void onInteractive() {
+        if (DEBUG) {
+            Slog.d(TAG, "onInteractive");
+        }
+
+        synchronized (mLock) {
+            cancelStandbyTimeoutAlarmLocked();
+            mIsInteractive = true;
+            mIsDeviceIdle = false;
+            mIdleSinceNonInteractive = false;
+            updateActiveLocked();
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void scheduleStandbyTimeoutAlarmLocked() {
+        final long nextAlarmTime = SystemClock.elapsedRealtime() + mStandbyTimeoutConfig;
+        mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                nextAlarmTime, "LowPowerStandbyController.StandbyTimeout",
+                mOnStandbyTimeoutExpired, mHandler);
+    }
+
+    @GuardedBy("mLock")
+    private void cancelStandbyTimeoutAlarmLocked() {
+        mAlarmManager.cancel(mOnStandbyTimeoutExpired);
+    }
+
+    private void onDeviceIdleModeChanged() {
+        synchronized (mLock) {
+            mIsDeviceIdle = mPowerManager.isDeviceIdleMode();
+            if (DEBUG) {
+                Slog.d(TAG, "onDeviceIdleModeChanged, mIsDeviceIdle=" + mIsDeviceIdle);
+            }
+
+            mIdleSinceNonInteractive = mIdleSinceNonInteractive || mIsDeviceIdle;
+            updateActiveLocked();
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void onEnabledLocked() {
+        if (DEBUG) {
+            Slog.d(TAG, "onEnabledLocked");
+        }
+
+        if (mPowerManager.isInteractive()) {
+            onInteractive();
+        } else {
+            onNonInteractive();
+        }
+
+        registerBroadcastReceiver();
+    }
+
+    @GuardedBy("mLock")
+    private void onDisabledLocked() {
+        if (DEBUG) {
+            Slog.d(TAG, "onDisabledLocked");
+        }
+
+        cancelStandbyTimeoutAlarmLocked();
+        unregisterBroadcastReceiver();
+        updateActiveLocked();
+    }
+
+    @VisibleForTesting
+    void onSettingsChanged() {
+        if (DEBUG) {
+            Slog.d(TAG, "onSettingsChanged");
+        }
+        synchronized (mLock) {
+            final boolean oldEnabled = mIsEnabled;
+            updateSettingsLocked();
+
+            if (mIsEnabled != oldEnabled) {
+                if (mIsEnabled) {
+                    onEnabledLocked();
+                } else {
+                    onDisabledLocked();
+                }
+
+                notifyEnabledChangedLocked();
+            }
+        }
+    }
+
+    private void registerBroadcastReceiver() {
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+        intentFilter.addAction(Intent.ACTION_SCREEN_ON);
+        intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
+
+        mContext.registerReceiver(mBroadcastReceiver, intentFilter);
+    }
+
+    private void unregisterBroadcastReceiver() {
+        mContext.unregisterReceiver(mBroadcastReceiver);
+    }
+
+    @GuardedBy("mLock")
+    private void notifyEnabledChangedLocked() {
+        if (DEBUG) {
+            Slog.d(TAG, "notifyEnabledChangedLocked, mIsEnabled=" + mIsEnabled);
+        }
+
+        final Intent intent = new Intent(PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
+        mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+    }
+
+    private void onStandbyTimeoutExpired() {
+        if (DEBUG) {
+            Slog.d(TAG, "onStandbyTimeoutExpired");
+        }
+        synchronized (mLock) {
+            updateActiveLocked();
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void enqueueNotifyActiveChangedLocked() {
+        final long now = mClock.elapsedRealtime();
+        final Message msg = mHandler.obtainMessage(MSG_NOTIFY_ACTIVE_CHANGED, mIsActive);
+        mHandler.sendMessageAtTime(msg, now);
+    }
+
+    /** Notify other system components about the updated Low Power Standby active state */
+    private void notifyActiveChanged(boolean active) {
+        final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class);
+        pmi.setLowPowerStandbyActive(active);
+    }
+
+    @VisibleForTesting
+    boolean isActive() {
+        synchronized (mLock) {
+            return mIsActive;
+        }
+    }
+
+    boolean isSupported() {
+        synchronized (mLock) {
+            return mSupportedConfig;
+        }
+    }
+
+    boolean isEnabled() {
+        synchronized (mLock) {
+            return mSupportedConfig && mIsEnabled;
+        }
+    }
+
+    void setEnabled(boolean enabled) {
+        synchronized (mLock) {
+            if (!mSupportedConfig) {
+                Slog.w(TAG, "Low Power Standby cannot be enabled "
+                        + "because it is not supported on this device");
+                return;
+            }
+
+            Settings.Global.putInt(mContext.getContentResolver(),
+                    Settings.Global.LOW_POWER_STANDBY_ENABLED, enabled ? 1 : 0);
+            onSettingsChanged();
+        }
+    }
+
+    void setActiveDuringMaintenance(boolean activeDuringMaintenance) {
+        synchronized (mLock) {
+            if (!mSupportedConfig) {
+                Slog.w(TAG, "Low Power Standby settings cannot be changed "
+                        + "because it is not supported on this device");
+                return;
+            }
+
+            Settings.Global.putInt(mContext.getContentResolver(),
+                    Settings.Global.LOW_POWER_STANDBY_ACTIVE_DURING_MAINTENANCE,
+                    activeDuringMaintenance ? 1 : 0);
+            onSettingsChanged();
+        }
+    }
+
+    void forceActive(boolean active) {
+        synchronized (mLock) {
+            mForceActive = active;
+            updateActiveLocked();
+        }
+    }
+
+    void dump(PrintWriter pw) {
+        final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
+
+        ipw.println();
+        ipw.println("Low Power Standby Controller:");
+        ipw.increaseIndent();
+        synchronized (mLock) {
+            ipw.print("mIsActive=");
+            ipw.println(mIsActive);
+            ipw.print("mIsEnabled=");
+            ipw.println(mIsEnabled);
+            ipw.print("mSupportedConfig=");
+            ipw.println(mSupportedConfig);
+            ipw.print("mEnabledByDefaultConfig=");
+            ipw.println(mEnabledByDefaultConfig);
+            ipw.print("mStandbyTimeoutConfig=");
+            ipw.println(mStandbyTimeoutConfig);
+
+            if (mIsActive || mIsEnabled) {
+                ipw.print("mIsInteractive=");
+                ipw.println(mIsInteractive);
+                ipw.print("mLastInteractiveTime=");
+                ipw.println(mLastInteractiveTimeElapsed);
+                ipw.print("mIdleSinceNonInteractive=");
+                ipw.println(mIdleSinceNonInteractive);
+                ipw.print("mIsDeviceIdle=");
+                ipw.println(mIsDeviceIdle);
+            }
+
+            final int[] allowlistUids = getAllowlistUidsLocked();
+            ipw.print("mAllowlistUids=");
+            ipw.println(Arrays.toString(allowlistUids));
+        }
+        ipw.decreaseIndent();
+    }
+
+    void dumpProto(ProtoOutputStream proto, long tag) {
+        synchronized (mLock) {
+            final long token = proto.start(tag);
+            proto.write(LowPowerStandbyControllerDumpProto.IS_ACTIVE, mIsActive);
+            proto.write(LowPowerStandbyControllerDumpProto.IS_ENABLED, mIsEnabled);
+            proto.write(LowPowerStandbyControllerDumpProto.IS_SUPPORTED_CONFIG, mSupportedConfig);
+            proto.write(LowPowerStandbyControllerDumpProto.IS_ENABLED_BY_DEFAULT_CONFIG,
+                    mEnabledByDefaultConfig);
+            proto.write(LowPowerStandbyControllerDumpProto.IS_INTERACTIVE, mIsInteractive);
+            proto.write(LowPowerStandbyControllerDumpProto.LAST_INTERACTIVE_TIME,
+                    mLastInteractiveTimeElapsed);
+            proto.write(LowPowerStandbyControllerDumpProto.STANDBY_TIMEOUT_CONFIG,
+                    mStandbyTimeoutConfig);
+            proto.write(LowPowerStandbyControllerDumpProto.IDLE_SINCE_NON_INTERACTIVE,
+                    mIdleSinceNonInteractive);
+            proto.write(LowPowerStandbyControllerDumpProto.IS_DEVICE_IDLE, mIsDeviceIdle);
+
+            final int[] allowlistUids = getAllowlistUidsLocked();
+            for (int appId : allowlistUids) {
+                proto.write(LowPowerStandbyControllerDumpProto.ALLOWLIST, appId);
+            }
+
+            proto.end(token);
+        }
+    }
+
+    private class LowPowerStandbyHandler extends Handler {
+        LowPowerStandbyHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_STANDBY_TIMEOUT:
+                    onStandbyTimeoutExpired();
+                    break;
+                case MSG_NOTIFY_ACTIVE_CHANGED:
+                    boolean active = (boolean) msg.obj;
+                    notifyActiveChanged(active);
+                    break;
+                case MSG_NOTIFY_ALLOWLIST_CHANGED:
+                    final int[] allowlistUids = (int[]) msg.obj;
+                    notifyAllowlistChanged(allowlistUids);
+                    break;
+            }
+        }
+    }
+
+    private void addToAllowlistInternal(int uid) {
+        if (DEBUG) {
+            Slog.i(TAG, "Adding to allowlist: " + uid);
+        }
+        synchronized (mLock) {
+            if (mSupportedConfig && !mAllowlistUids.get(uid)) {
+                mAllowlistUids.append(uid, true);
+                enqueueNotifyAllowlistChangedLocked();
+            }
+        }
+    }
+
+    private void removeFromAllowlistInternal(int uid) {
+        if (DEBUG) {
+            Slog.i(TAG, "Removing from allowlist: " + uid);
+        }
+        synchronized (mLock) {
+            if (mSupportedConfig && mAllowlistUids.get(uid)) {
+                mAllowlistUids.delete(uid);
+                enqueueNotifyAllowlistChangedLocked();
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private int[] getAllowlistUidsLocked() {
+        final int[] uids = new int[mAllowlistUids.size()];
+        for (int i = 0; i < mAllowlistUids.size(); i++) {
+            uids[i] = mAllowlistUids.keyAt(i);
+        }
+        return uids;
+    }
+
+    @GuardedBy("mLock")
+    private void enqueueNotifyAllowlistChangedLocked() {
+        final long now = mClock.elapsedRealtime();
+        final int[] allowlistUids = getAllowlistUidsLocked();
+        final Message msg = mHandler.obtainMessage(MSG_NOTIFY_ALLOWLIST_CHANGED, allowlistUids);
+        mHandler.sendMessageAtTime(msg, now);
+    }
+
+    private void notifyAllowlistChanged(int[] allowlistUids) {
+        final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class);
+        pmi.setLowPowerStandbyAllowlist(allowlistUids);
+    }
+
+    private final class LocalService extends LowPowerStandbyControllerInternal {
+        @Override
+        public void addToAllowlist(int uid) {
+            addToAllowlistInternal(uid);
+        }
+
+        @Override
+        public void removeFromAllowlist(int uid) {
+            removeFromAllowlistInternal(uid);
+        }
+    }
+
+    private final class SettingsObserver extends ContentObserver {
+        SettingsObserver(Handler handler) {
+            super(handler);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            onSettingsChanged();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/power/LowPowerStandbyControllerInternal.java b/services/core/java/com/android/server/power/LowPowerStandbyControllerInternal.java
new file mode 100644
index 0000000..f6953fa
--- /dev/null
+++ b/services/core/java/com/android/server/power/LowPowerStandbyControllerInternal.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power;
+
+/**
+ * @hide Only for use within the system server.
+ */
+public abstract class LowPowerStandbyControllerInternal {
+    /**
+     * Adds an application to the Low Power Standby allowlist,
+     * exempting it from Low Power Standby restrictions.
+     *
+     * @param uid UID to add to allowlist.
+     */
+    public abstract void addToAllowlist(int uid);
+
+    /**
+     * Removes an application from the Low Power Standby allowlist.
+     *
+     * @param uid UID to remove from allowlist.
+     */
+    public abstract void removeFromAllowlist(int uid);
+}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 4185b2d9..efcfbdd 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -37,6 +37,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.SynchronousUserSwitchObserver;
@@ -276,6 +277,7 @@
     private final BatterySaverPolicy mBatterySaverPolicy;
     private final BatterySaverStateMachine mBatterySaverStateMachine;
     private final BatterySavingStats mBatterySavingStats;
+    private final LowPowerStandbyController mLowPowerStandbyController;
     private final AttentionDetector mAttentionDetector;
     private final FaceDownDetector mFaceDownDetector;
     private final ScreenUndimDetector mScreenUndimDetector;
@@ -609,12 +611,17 @@
     // True if we are currently in light device idle mode.
     private boolean mLightDeviceIdleMode;
 
-    // Set of app ids that we will always respect the wake locks for.
+    // Set of app ids that we will respect the wake locks for while in device idle mode.
     int[] mDeviceIdleWhitelist = new int[0];
 
     // Set of app ids that are temporarily allowed to acquire wakelocks due to high-pri message
     int[] mDeviceIdleTempWhitelist = new int[0];
 
+    // Set of app ids that are allowed to acquire wakelocks while low power standby is active
+    int[] mLowPowerStandbyAllowlist = new int[0];
+
+    private boolean mLowPowerStandbyActive;
+
     private final SparseArray<UidState> mUidState = new SparseArray<>();
 
     // A mapping from DisplayGroup Id to PowerGroup. There is a 1-1 mapping between DisplayGroups
@@ -966,6 +973,10 @@
         void invalidateIsInteractiveCaches() {
             PowerManager.invalidateIsInteractiveCaches();
         }
+
+        LowPowerStandbyController createLowPowerStandbyController(Context context, Looper looper) {
+            return new LowPowerStandbyController(context, looper, SystemClock::elapsedRealtime);
+        }
     }
 
     final Constants mConstants;
@@ -1015,6 +1026,8 @@
         mBatterySaverStateMachine = mInjector.createBatterySaverStateMachine(mLock, mContext,
                 mBatterySaverController);
 
+        mLowPowerStandbyController = mInjector.createLowPowerStandbyController(mContext,
+                Looper.getMainLooper());
         mInattentiveSleepWarningOverlayController =
                 mInjector.createInattentiveSleepWarningController();
 
@@ -1228,6 +1241,8 @@
                 // Shouldn't happen since in-process.
             }
 
+            mLowPowerStandbyController.systemReady();
+
             // Go.
             readConfigurationLocked();
             updateSettingsLocked();
@@ -1654,6 +1669,16 @@
     }
 
     @GuardedBy("mLock")
+    @VisibleForTesting
+    WakeLock findWakeLockLocked(IBinder lock) {
+        int index = findWakeLockIndexLocked(lock);
+        if (index == -1) {
+            return null;
+        }
+        return mWakeLocks.get(index);
+    }
+
+    @GuardedBy("mLock")
     private void notifyWakeLockAcquiredLocked(WakeLock wakeLock) {
         if (mSystemReady && !wakeLock.mDisabled) {
             wakeLock.mNotifiedAcquired = true;
@@ -3852,6 +3877,24 @@
         }
     }
 
+    void setLowPowerStandbyAllowlistInternal(int[] appids) {
+        synchronized (mLock) {
+            mLowPowerStandbyAllowlist = appids;
+            if (mLowPowerStandbyActive) {
+                updateWakeLockDisabledStatesLocked();
+            }
+        }
+    }
+
+    void setLowPowerStandbyActiveInternal(boolean active) {
+        synchronized (mLock) {
+            if (mLowPowerStandbyActive != active) {
+                mLowPowerStandbyActive = active;
+                updateWakeLockDisabledStatesLocked();
+            }
+        }
+    }
+
     void startUidChangesInternal() {
         synchronized (mLock) {
             mUidsChanging = true;
@@ -3888,7 +3931,7 @@
                     <= ActivityManager.PROCESS_STATE_RECEIVER;
             state.mProcState = procState;
             if (state.mNumWakeLocks > 0) {
-                if (mDeviceIdleMode) {
+                if (mDeviceIdleMode || mLowPowerStandbyActive) {
                     handleUidStateChangeLocked();
                 } else if (!state.mActive && oldShouldAllow !=
                         (procState <= ActivityManager.PROCESS_STATE_RECEIVER)) {
@@ -3908,7 +3951,7 @@
                 state.mProcState = ActivityManager.PROCESS_STATE_NONEXISTENT;
                 state.mActive = false;
                 mUidState.removeAt(index);
-                if (mDeviceIdleMode && state.mNumWakeLocks > 0) {
+                if ((mDeviceIdleMode || mLowPowerStandbyActive) && state.mNumWakeLocks > 0) {
                     handleUidStateChangeLocked();
                 }
             }
@@ -3993,6 +4036,14 @@
                         disabled = true;
                     }
                 }
+                if (mLowPowerStandbyActive) {
+                    final UidState state = wakeLock.mUidState;
+                    if (Arrays.binarySearch(mLowPowerStandbyAllowlist, appid) < 0
+                            && state.mProcState != ActivityManager.PROCESS_STATE_NONEXISTENT
+                            && state.mProcState > ActivityManager.PROCESS_STATE_BOUND_TOP) {
+                        disabled = true;
+                    }
+                }
             }
             if (wakeLock.mDisabled != disabled) {
                 wakeLock.mDisabled = disabled;
@@ -4260,6 +4311,7 @@
         pw.println("POWER MANAGER (dumpsys power)\n");
 
         final WirelessChargerDetector wcd;
+        final LowPowerStandbyController lowPowerStandbyController;
         synchronized (mLock) {
             pw.println("Power Manager State:");
             mConstants.dump(pw);
@@ -4316,6 +4368,7 @@
             pw.println("  mDeviceIdleMode=" + mDeviceIdleMode);
             pw.println("  mDeviceIdleWhitelist=" + Arrays.toString(mDeviceIdleWhitelist));
             pw.println("  mDeviceIdleTempWhitelist=" + Arrays.toString(mDeviceIdleTempWhitelist));
+            pw.println("  mLowPowerStandbyActive=" + mLowPowerStandbyActive);
             pw.println("  mLastWakeTime=" + TimeUtils.formatUptime(mLastGlobalWakeTime));
             pw.println("  mLastSleepTime=" + TimeUtils.formatUptime(mLastGlobalSleepTime));
             pw.println("  mLastSleepReason=" + PowerManager.sleepReasonToString(
@@ -4491,10 +4544,13 @@
         mFaceDownDetector.dump(pw);
 
         mAmbientDisplaySuppressionController.dump(pw);
+
+        mLowPowerStandbyController.dump(pw);
     }
 
     private void dumpProto(FileDescriptor fd) {
         final WirelessChargerDetector wcd;
+        final LowPowerStandbyController lowPowerStandbyController;
         final ProtoOutputStream proto = new ProtoOutputStream(fd);
 
         synchronized (mLock) {
@@ -4599,6 +4655,9 @@
                 proto.write(PowerManagerServiceDumpProto.DEVICE_IDLE_TEMP_WHITELIST, id);
             }
 
+            proto.write(PowerManagerServiceDumpProto.IS_LOW_POWER_STANDBY_ACTIVE,
+                    mLowPowerStandbyActive);
+
             proto.write(PowerManagerServiceDumpProto.LAST_WAKE_TIME_MS, mLastGlobalWakeTime);
             proto.write(PowerManagerServiceDumpProto.LAST_SLEEP_TIME_MS, mLastGlobalSleepTime);
             proto.write(
@@ -4832,6 +4891,7 @@
             for (SuspendBlocker sb : mSuspendBlockers) {
                 sb.dumpDebug(proto, PowerManagerServiceDumpProto.SUSPEND_BLOCKERS);
             }
+
             wcd = mWirelessChargerDetector;
         }
 
@@ -4839,6 +4899,9 @@
             wcd.dumpDebug(proto, PowerManagerServiceDumpProto.WIRELESS_CHARGER_DETECTOR);
         }
 
+        mLowPowerStandbyController.dumpProto(proto,
+                PowerManagerServiceDumpProto.LOW_POWER_STANDBY_CONTROLLER);
+
         proto.flush();
     }
 
@@ -5092,6 +5155,8 @@
                     (mFlags & PowerManager.ACQUIRE_CAUSES_WAKEUP)!=0);
             proto.write(WakeLockProto.WakeLockFlagsProto.IS_ON_AFTER_RELEASE,
                     (mFlags & PowerManager.ON_AFTER_RELEASE)!=0);
+            proto.write(WakeLockProto.WakeLockFlagsProto.SYSTEM_WAKELOCK,
+                    (mFlags & PowerManager.SYSTEM_WAKELOCK) != 0);
             proto.end(wakeLockFlagsToken);
 
             proto.write(WakeLockProto.IS_DISABLED, mDisabled);
@@ -5138,6 +5203,9 @@
             if ((mFlags & PowerManager.ON_AFTER_RELEASE) != 0) {
                 result += " ON_AFTER_RELEASE";
             }
+            if ((mFlags & PowerManager.SYSTEM_WAKELOCK) != 0) {
+                result += " SYSTEM_WAKELOCK";
+            }
             return result;
         }
     }
@@ -5299,8 +5367,22 @@
                 ws = null;
             }
 
-            final int uid = Binder.getCallingUid();
-            final int pid = Binder.getCallingPid();
+            int uid = Binder.getCallingUid();
+            int pid = Binder.getCallingPid();
+
+            if ((flags & PowerManager.SYSTEM_WAKELOCK) != 0) {
+                mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
+                        null);
+                WorkSource workSource = new WorkSource(Binder.getCallingUid(), packageName);
+                if (ws != null && !ws.isEmpty()) {
+                    workSource.add(ws);
+                }
+                ws = workSource;
+
+                uid = Process.myUid();
+                pid = Process.myPid();
+            }
+
             final long ident = Binder.clearCallingIdentity();
             try {
                 acquireWakeLockInternal(lock, displayId, flags, tag, packageName, ws, historyTag,
@@ -5768,6 +5850,100 @@
             }
         }
 
+        @Override // Binder call
+        @RequiresPermission(anyOf = {
+                android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+                android.Manifest.permission.DEVICE_POWER
+        })
+        public boolean isLowPowerStandbySupported() {
+            if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER)
+                    != PackageManager.PERMISSION_GRANTED) {
+                mContext.enforceCallingOrSelfPermission(
+                        android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+                        "isLowPowerStandbySupported");
+            }
+
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                return mLowPowerStandbyController.isSupported();
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        @Override // Binder call
+        public boolean isLowPowerStandbyEnabled() {
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                return mLowPowerStandbyController.isEnabled();
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        @Override // Binder call
+        @RequiresPermission(anyOf = {
+                android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+                android.Manifest.permission.DEVICE_POWER
+        })
+        public void setLowPowerStandbyEnabled(boolean enabled) {
+            if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER)
+                    != PackageManager.PERMISSION_GRANTED) {
+                mContext.enforceCallingOrSelfPermission(
+                        android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+                        "setLowPowerStandbyEnabled");
+            }
+
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                mLowPowerStandbyController.setEnabled(enabled);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        @Override // Binder call
+        @RequiresPermission(anyOf = {
+                android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+                android.Manifest.permission.DEVICE_POWER
+        })
+        public void setLowPowerStandbyActiveDuringMaintenance(boolean activeDuringMaintenance) {
+            if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER)
+                    != PackageManager.PERMISSION_GRANTED) {
+                mContext.enforceCallingOrSelfPermission(
+                        android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+                        "setLowPowerStandbyActiveDuringMaintenance");
+            }
+
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                mLowPowerStandbyController.setActiveDuringMaintenance(activeDuringMaintenance);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        @Override // Binder call
+        @RequiresPermission(anyOf = {
+                android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+                android.Manifest.permission.DEVICE_POWER
+        })
+        public void forceLowPowerStandbyActive(boolean active) {
+            if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER)
+                    != PackageManager.PERMISSION_GRANTED) {
+                mContext.enforceCallingOrSelfPermission(
+                        android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+                        "forceLowPowerStandbyActive");
+            }
+
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                mLowPowerStandbyController.forceActive(active);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
         /**
          * Gets the reason for the last time the phone had to reboot.
          *
@@ -6249,6 +6425,16 @@
         }
 
         @Override
+        public void setLowPowerStandbyAllowlist(int[] appids) {
+            setLowPowerStandbyAllowlistInternal(appids);
+        }
+
+        @Override
+        public void setLowPowerStandbyActive(boolean enabled) {
+            setLowPowerStandbyActiveInternal(enabled);
+        }
+
+        @Override
         public void startUidChanges() {
             startUidChangesInternal();
         }
diff --git a/services/core/java/com/android/server/power/WakeLockLog.java b/services/core/java/com/android/server/power/WakeLockLog.java
index 88c9850..d20c7f1 100644
--- a/services/core/java/com/android/server/power/WakeLockLog.java
+++ b/services/core/java/com/android/server/power/WakeLockLog.java
@@ -106,6 +106,7 @@
      */
     private static final int FLAG_ON_AFTER_RELEASE = 0x8;
     private static final int FLAG_ACQUIRE_CAUSES_WAKEUP = 0x10;
+    private static final int FLAG_SYSTEM_WAKELOCK = 0x20;
 
     private static final int MASK_LOWER_6_BITS = 0x3F;
     private static final int MASK_LOWER_7_BITS = 0x7F;
@@ -296,6 +297,9 @@
         if ((flags & PowerManager.ON_AFTER_RELEASE) != 0) {
             newFlags |= FLAG_ON_AFTER_RELEASE;
         }
+        if ((flags & PowerManager.SYSTEM_WAKELOCK) != 0) {
+            newFlags |= FLAG_SYSTEM_WAKELOCK;
+        }
         return newFlags;
     }
 
@@ -455,6 +459,9 @@
             if ((flags & FLAG_ACQUIRE_CAUSES_WAKEUP) == FLAG_ACQUIRE_CAUSES_WAKEUP) {
                 sb.append(",acq-causes-wake");
             }
+            if ((flags & FLAG_SYSTEM_WAKELOCK) == FLAG_SYSTEM_WAKELOCK) {
+                sb.append(",system-wakelock");
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index e71ff78..344edbb 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -47,6 +47,7 @@
 import android.graphics.drawable.Icon;
 import android.hardware.biometrics.BiometricAuthenticator.Modality;
 import android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode;
+import android.hardware.biometrics.IBiometricContextListener;
 import android.hardware.biometrics.IBiometricSysuiReceiver;
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.display.DisplayManager;
@@ -894,6 +895,17 @@
     }
 
     @Override
+    public void setBiometicContextListener(IBiometricContextListener listener) {
+        enforceStatusBarService();
+        if (mBar != null) {
+            try {
+                mBar.setBiometicContextListener(listener);
+            } catch (RemoteException ex) {
+            }
+        }
+    }
+
+    @Override
     public void setUdfpsHbmListener(IUdfpsHbmListener listener) {
         enforceStatusBarService();
         if (mBar != null) {
diff --git a/services/core/java/com/android/server/tracing/TracingServiceProxy.java b/services/core/java/com/android/server/tracing/TracingServiceProxy.java
index ff2f08b..27c0bee 100644
--- a/services/core/java/com/android/server/tracing/TracingServiceProxy.java
+++ b/services/core/java/com/android/server/tracing/TracingServiceProxy.java
@@ -15,34 +15,56 @@
  */
 package com.android.server.tracing;
 
+import android.Manifest;
+import android.annotation.NonNull;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ServiceInfo;
 import android.os.Binder;
+import android.os.IMessenger;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.ParcelFileDescriptor.AutoCloseInputStream;
+import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
 import android.os.UserHandle;
+import android.service.tracing.TraceReportService;
 import android.tracing.ITracingServiceProxy;
+import android.tracing.TraceReportParams;
 import android.util.Log;
+import android.util.LruCache;
+import android.util.Slog;
 
+import com.android.internal.infra.ServiceConnector;
 import com.android.server.SystemService;
 
+import java.io.IOException;
+
 /**
  * TracingServiceProxy is the system_server intermediary between the Perfetto tracing daemon and the
- * system tracing app Traceur.
+ * other components (e.g. system tracing app Traceur, trace reporting apps).
  *
  * Access to this service is restricted via SELinux. Normal apps do not have access.
  *
  * @hide
  */
 public class TracingServiceProxy extends SystemService {
-    private static final String TAG = "TracingServiceProxy";
-
     public static final String TRACING_SERVICE_PROXY_BINDER_NAME = "tracing.proxy";
-
+    private static final String TAG = "TracingServiceProxy";
     private static final String TRACING_APP_PACKAGE_NAME = "com.android.traceur";
     private static final String TRACING_APP_ACTIVITY = "com.android.traceur.StopTraceService";
 
+    private static final int MAX_CACHED_REPORTER_SERVICES = 8;
+
+    // The maximum size of the trace allowed if the option |usePipeForTesting| is set.
+    // Note: this size MUST be smaller than the buffer size of the pipe (i.e. what you can
+    // write to the pipe without blocking) to avoid system_server blocking on this.
+    // (on Linux, the minimum value is 4K i.e. 1 minimally sized page).
+    private static final int MAX_FILE_SIZE_BYTES_TO_PIPE = 1024;
+
     // Keep this in sync with the definitions in TraceService
     private static final String INTENT_ACTION_NOTIFY_SESSION_STOPPED =
             "com.android.traceur.NOTIFY_SESSION_STOPPED";
@@ -51,16 +73,22 @@
 
     private final Context mContext;
     private final PackageManager mPackageManager;
+    private final LruCache<ComponentName, ServiceConnector<IMessenger>> mCachedReporterServices;
 
     private final ITracingServiceProxy.Stub mTracingServiceProxy = new ITracingServiceProxy.Stub() {
         /**
-          * Notifies system tracing app that a tracing session has ended. If a session is repurposed
-          * for use in a bugreport, sessionStolen can be set to indicate that tracing has ended but
-          * there is no buffer available to dump.
-          */
+         * Notifies system tracing app that a tracing session has ended. If a session is repurposed
+         * for use in a bugreport, sessionStolen can be set to indicate that tracing has ended but
+         * there is no buffer available to dump.
+         */
         @Override
         public void notifyTraceSessionEnded(boolean sessionStolen) {
-            notifyTraceur(sessionStolen);
+            TracingServiceProxy.this.notifyTraceur(sessionStolen);
+        }
+
+        @Override
+        public void reportTrace(@NonNull TraceReportParams params) {
+            TracingServiceProxy.this.reportTrace(params);
         }
     };
 
@@ -68,6 +96,7 @@
         super(context);
         mContext = context;
         mPackageManager = context.getPackageManager();
+        mCachedReporterServices = new LruCache<>(MAX_CACHED_REPORTER_SERVICES);
     }
 
     @Override
@@ -103,4 +132,119 @@
             Log.e(TAG, "Failed to locate Traceur", e);
         }
     }
+
+    private void reportTrace(@NonNull TraceReportParams params) {
+        // We don't need to do any permission checks on the caller because access
+        // to this service is guarded by SELinux.
+        ComponentName component = new ComponentName(params.reporterPackageName,
+                params.reporterClassName);
+        if (!hasBindServicePermission(component)) {
+            return;
+        }
+        boolean hasDumpPermission = hasPermission(component, Manifest.permission.DUMP);
+        boolean hasUsageStatsPermission = hasPermission(component,
+                Manifest.permission.PACKAGE_USAGE_STATS);
+        if (!hasDumpPermission || !hasUsageStatsPermission) {
+            return;
+        }
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            reportTrace(getOrCreateReporterService(component), params);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private void reportTrace(
+            @NonNull ServiceConnector<IMessenger> reporterService,
+            @NonNull TraceReportParams params) {
+        reporterService.post(messenger -> {
+            if (params.usePipeForTesting) {
+                ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
+                try (AutoCloseInputStream i = new AutoCloseInputStream(params.fd)) {
+                    try (AutoCloseOutputStream o = new AutoCloseOutputStream(pipe[1])) {
+                        byte[] array = i.readNBytes(MAX_FILE_SIZE_BYTES_TO_PIPE);
+                        if (array.length == MAX_FILE_SIZE_BYTES_TO_PIPE) {
+                            throw new IllegalArgumentException(
+                                    "Trace file too large when |usePipeForTesting| is set.");
+                        }
+                        o.write(array);
+                    }
+                }
+                params.fd = pipe[0];
+            }
+
+            Message message = Message.obtain();
+            message.what = TraceReportService.MSG_REPORT_TRACE;
+            message.obj = params;
+            messenger.send(message);
+        }).whenComplete((res, err) -> {
+            if (err != null) {
+                Slog.e(TAG, "Failed to report trace", err);
+            }
+            try {
+                params.fd.close();
+            } catch (IOException ignored) {
+            }
+        });
+    }
+
+    private ServiceConnector<IMessenger> getOrCreateReporterService(
+            @NonNull ComponentName component) {
+        ServiceConnector<IMessenger> connector = mCachedReporterServices.get(component);
+        if (connector == null) {
+            Intent intent = new Intent();
+            intent.setComponent(component);
+            connector = new ServiceConnector.Impl<IMessenger>(
+                    mContext, intent,
+                    Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY,
+                    mContext.getUser().getIdentifier(), IMessenger.Stub::asInterface) {
+                private static final long DISCONNECT_TIMEOUT_MS = 15_000;
+                private static final long REQUEST_TIMEOUT_MS = 10_000;
+
+                @Override
+                protected long getAutoDisconnectTimeoutMs() {
+                    return DISCONNECT_TIMEOUT_MS;
+                }
+
+                @Override
+                protected long getRequestTimeoutMs() {
+                    return REQUEST_TIMEOUT_MS;
+                }
+            };
+            mCachedReporterServices.put(intent.getComponent(), connector);
+        }
+        return connector;
+    }
+
+    private boolean hasPermission(@NonNull ComponentName componentName,
+            @NonNull String permission) throws SecurityException {
+        if (mPackageManager.checkPermission(permission, componentName.getPackageName())
+                != PackageManager.PERMISSION_GRANTED) {
+            Slog.e(TAG,
+                    "Trace reporting service " + componentName.toShortString() + " does not have "
+                            + permission + " permission");
+            return false;
+        }
+        return true;
+    }
+
+    private boolean hasBindServicePermission(@NonNull ComponentName componentName) {
+        ServiceInfo info;
+        try {
+            info = mPackageManager.getServiceInfo(componentName, 0);
+        } catch (NameNotFoundException ex) {
+            Slog.e(TAG,
+                    "Trace reporting service " + componentName.toShortString() + " does not exist");
+            return false;
+        }
+        if (!Manifest.permission.BIND_TRACE_REPORT_SERVICE.equals(info.permission)) {
+            Slog.e(TAG,
+                    "Trace reporting service " + componentName.toShortString()
+                            + " does not request " + Manifest.permission.BIND_TRACE_REPORT_SERVICE
+                            + " permission; instead requests " + info.permission);
+            return false;
+        }
+        return true;
+    }
 }
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 9bed24d..52f7d10 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -122,7 +122,8 @@
     private static final int MSG_DISPATCH_UNLOCK_LOCKOUT = 13;
     private static final int MSG_REFRESH_DEVICE_LOCKED_FOR_USER = 14;
     private static final int MSG_SCHEDULE_TRUST_TIMEOUT = 15;
-    public static final int MSG_USER_REQUESTED_UNLOCK = 16;
+    private static final int MSG_USER_REQUESTED_UNLOCK = 16;
+    private static final int MSG_ENABLE_TRUST_AGENT = 17;
 
     private static final String REFRESH_DEVICE_LOCKED_EXCEPT_USER = "except";
 
@@ -631,6 +632,24 @@
         }
     }
 
+
+    /**
+     * Uses {@link LockPatternUtils} to enable the setting for trust agent in the specified
+     * component name. This should only be used for testing.
+     */
+    private void enableTrustAgentForUserForTest(@NonNull ComponentName componentName, int userId) {
+        Log.i(TAG,
+                "Enabling trust agent " + componentName.flattenToString() + " for user " + userId);
+        List<ComponentName> agents =
+                new ArrayList<>(mLockPatternUtils.getEnabledTrustAgents(userId));
+        if (!agents.contains(componentName)) {
+            agents.add(componentName);
+        }
+        // Even if the agent was already there, we still call setEnabledTrustAgents to trigger a
+        // refresh of installed agents.
+        mLockPatternUtils.setEnabledTrustAgents(agents, userId);
+    }
+
     boolean isDeviceLockedInner(int userId) {
         synchronized (mDeviceLockedForUser) {
             return mDeviceLockedForUser.get(userId, true);
@@ -929,6 +948,7 @@
                 continue;
             }
             allowedAgents.add(resolveInfo);
+            if (DEBUG) Slog.d(TAG, "Adding agent " + getComponentName(resolveInfo));
         }
         return allowedAgents;
     }
@@ -1158,6 +1178,13 @@
         }
 
         @Override
+        public void enableTrustAgentForUserForTest(ComponentName componentName, int userId)
+                throws RemoteException {
+            enforceReportPermission();
+            mHandler.obtainMessage(MSG_ENABLE_TRUST_AGENT, userId, 0, componentName).sendToTarget();
+        }
+
+        @Override
         public void reportKeyguardShowingChanged() throws RemoteException {
             enforceReportPermission();
             // coalesce refresh messages.
@@ -1433,6 +1460,9 @@
                     // This is also called when the security mode of a user changes.
                     refreshDeviceLockedForUser(UserHandle.USER_ALL);
                     break;
+                case MSG_ENABLE_TRUST_AGENT:
+                    enableTrustAgentForUserForTest((ComponentName) msg.obj, msg.arg1);
+                    break;
                 case MSG_KEYGUARD_SHOWING_CHANGED:
                     refreshDeviceLockedForUser(mCurrentUser);
                     break;
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 7164c6c..ff96aeb 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -43,6 +43,7 @@
 import static com.android.server.wm.ActivityTaskManagerService.TAG_SWITCH;
 import static com.android.server.wm.ActivityTaskManagerService.enforceNotIsolatedCaller;
 
+import android.annotation.ColorInt;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Activity;
@@ -1081,7 +1082,7 @@
 
     @Override
     public void overridePendingTransition(IBinder token, String packageName,
-            int enterAnim, int exitAnim) {
+            int enterAnim, int exitAnim, @ColorInt int backgroundColor) {
         final long origId = Binder.clearCallingIdentity();
         synchronized (mGlobalLock) {
             final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
@@ -1091,7 +1092,7 @@
                         r.mOverrideTaskTransition);
                 r.mTransitionController.setOverrideAnimation(
                         TransitionInfo.AnimationOptions.makeCustomAnimOptions(packageName,
-                                enterAnim, exitAnim, r.mOverrideTaskTransition),
+                                enterAnim, exitAnim, backgroundColor, r.mOverrideTaskTransition),
                         null /* startCallback */, null /* finishCallback */);
             }
         }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index c0eee61..f8d7b78 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4469,6 +4469,7 @@
                         pendingOptions.getOverrideTaskTransition());
                 options = AnimationOptions.makeCustomAnimOptions(pendingOptions.getPackageName(),
                         pendingOptions.getCustomEnterResId(), pendingOptions.getCustomExitResId(),
+                        pendingOptions.getCustomBackgroundColor(),
                         pendingOptions.getOverrideTaskTransition());
                 startCallback = pendingOptions.getAnimationStartedListener();
                 finishCallback = pendingOptions.getAnimationFinishedListener();
@@ -6928,7 +6929,7 @@
 
     @Override
     void prepareSurfaces() {
-        final boolean show = isVisible() || isAnimating(TRANSITION | PARENTS,
+        final boolean show = isVisible() || isAnimating(PARENTS,
                 ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS);
 
         if (mSurfaceControl != null) {
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index a049d65..d4a7a5d 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -380,8 +380,11 @@
         final ComponentName cn = ComponentName.unflattenFromString(rawRecentsComponent);
         if (cn != null) {
             try {
-                final ApplicationInfo appInfo = AppGlobals.getPackageManager()
-                        .getApplicationInfo(cn.getPackageName(), 0, mService.mContext.getUserId());
+                final ApplicationInfo appInfo = AppGlobals.getPackageManager().getApplicationInfo(
+                        cn.getPackageName(),
+                        PackageManager.MATCH_UNINSTALLED_PACKAGES
+                                | PackageManager.MATCH_DISABLED_COMPONENTS,
+                        mService.mContext.getUserId());
                 if (appInfo != null) {
                     mRecentsUid = appInfo.uid;
                     mRecentsComponent = cn;
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index ded58f4..ad45948 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1039,6 +1039,8 @@
                         "  Rejecting as detached: %s", wc);
                 continue;
             }
+            // The level of transition target should be at least window token.
+            if (wc.asWindowState() != null) continue;
 
             final ChangeInfo changeInfo = changes.get(wc);
 
diff --git a/services/core/jni/com_android_server_companion_virtual_InputController.cpp b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
index 43018a9..adc91fc 100644
--- a/services/core/jni/com_android_server_companion_virtual_InputController.cpp
+++ b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
@@ -186,7 +186,7 @@
 };
 
 /** Creates a new uinput device and assigns a file descriptor. */
-static int openUinput(const char* readableName, jint vendorId, jint productId,
+static int openUinput(const char* readableName, jint vendorId, jint productId, const char* phys,
                       DeviceType deviceType, jint screenHeight, jint screenWidth) {
     android::base::unique_fd fd(TEMP_FAILURE_RETRY(::open("/dev/uinput", O_WRONLY | O_NONBLOCK)));
     if (fd < 0) {
@@ -194,6 +194,8 @@
         return -errno;
     }
 
+    ioctl(fd, UI_SET_PHYS, phys);
+
     ioctl(fd, UI_SET_EVBIT, EV_KEY);
     ioctl(fd, UI_SET_EVBIT, EV_SYN);
     switch (deviceType) {
@@ -295,28 +297,30 @@
     return fd.release();
 }
 
-static int openUinputJni(JNIEnv* env, jstring name, jint vendorId, jint productId,
+static int openUinputJni(JNIEnv* env, jstring name, jint vendorId, jint productId, jstring phys,
                          DeviceType deviceType, int screenHeight, int screenWidth) {
     ScopedUtfChars readableName(env, name);
-    return openUinput(readableName.c_str(), vendorId, productId, deviceType, screenHeight,
-                      screenWidth);
+    ScopedUtfChars readablePhys(env, phys);
+    return openUinput(readableName.c_str(), vendorId, productId, readablePhys.c_str(), deviceType,
+                      screenHeight, screenWidth);
 }
 
 static int nativeOpenUinputKeyboard(JNIEnv* env, jobject thiz, jstring name, jint vendorId,
-                                    jint productId) {
-    return openUinputJni(env, name, vendorId, productId, DeviceType::KEYBOARD, /* screenHeight */ 0,
-                         /* screenWidth */ 0);
+                                    jint productId, jstring phys) {
+    return openUinputJni(env, name, vendorId, productId, phys, DeviceType::KEYBOARD,
+                         /* screenHeight */ 0, /* screenWidth */ 0);
 }
 
 static int nativeOpenUinputMouse(JNIEnv* env, jobject thiz, jstring name, jint vendorId,
-                                 jint productId) {
-    return openUinputJni(env, name, vendorId, productId, DeviceType::MOUSE, /* screenHeight */ 0,
-                         /* screenWidth */ 0);
+                                 jint productId, jstring phys) {
+    return openUinputJni(env, name, vendorId, productId, phys, DeviceType::MOUSE,
+                         /* screenHeight */ 0, /* screenWidth */ 0);
 }
 
 static int nativeOpenUinputTouchscreen(JNIEnv* env, jobject thiz, jstring name, jint vendorId,
-                                       jint productId, jint height, jint width) {
-    return openUinputJni(env, name, vendorId, productId, DeviceType::TOUCHSCREEN, height, width);
+                                       jint productId, jstring phys, jint height, jint width) {
+    return openUinputJni(env, name, vendorId, productId, phys, DeviceType::TOUCHSCREEN, height,
+                         width);
 }
 
 static bool nativeCloseUinput(JNIEnv* env, jobject thiz, jint fd) {
@@ -435,9 +439,11 @@
 }
 
 static JNINativeMethod methods[] = {
-        {"nativeOpenUinputKeyboard", "(Ljava/lang/String;II)I", (void*)nativeOpenUinputKeyboard},
-        {"nativeOpenUinputMouse", "(Ljava/lang/String;II)I", (void*)nativeOpenUinputMouse},
-        {"nativeOpenUinputTouchscreen", "(Ljava/lang/String;IIII)I",
+        {"nativeOpenUinputKeyboard", "(Ljava/lang/String;IILjava/lang/String;)I",
+         (void*)nativeOpenUinputKeyboard},
+        {"nativeOpenUinputMouse", "(Ljava/lang/String;IILjava/lang/String;)I",
+         (void*)nativeOpenUinputMouse},
+        {"nativeOpenUinputTouchscreen", "(Ljava/lang/String;IILjava/lang/String;II)I",
          (void*)nativeOpenUinputTouchscreen},
         {"nativeCloseUinput", "(I)Z", (void*)nativeCloseUinput},
         {"nativeWriteKeyEvent", "(III)Z", (void*)nativeWriteKeyEvent},
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 0da8f7e..161d7ce 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -1504,11 +1504,14 @@
         JNIEnv* /* env */, jclass, jint mode, jint recurrence, jint min_interval,
         jint preferred_accuracy, jint preferred_time, jboolean low_power_mode) {
     if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
-        auto status = gnssHalAidl->setPositionMode(static_cast<IGnssAidl::GnssPositionMode>(mode),
-                                                   static_cast<IGnssAidl::GnssPositionRecurrence>(
-                                                           recurrence),
-                                                   min_interval, preferred_accuracy, preferred_time,
-                                                   low_power_mode);
+        IGnssAidl::PositionModeOptions options;
+        options.mode = static_cast<IGnssAidl::GnssPositionMode>(mode);
+        options.recurrence = static_cast<IGnssAidl::GnssPositionRecurrence>(recurrence);
+        options.minIntervalMs = min_interval;
+        options.preferredAccuracyMeters = preferred_accuracy;
+        options.preferredTimeMs = preferred_time;
+        options.lowPowerMode = low_power_mode;
+        auto status = gnssHalAidl->setPositionMode(options);
         return checkAidlStatus(status, "IGnssAidl setPositionMode() failed.");
     }
 
diff --git a/services/core/jni/com_android_server_net_NetworkStatsService.cpp b/services/core/jni/com_android_server_net_NetworkStatsService.cpp
index 5178132..f8a8168 100644
--- a/services/core/jni/com_android_server_net_NetworkStatsService.cpp
+++ b/services/core/jni/com_android_server_net_NetworkStatsService.cpp
@@ -16,20 +16,18 @@
 
 #define LOG_TAG "NetworkStatsNative"
 
+#include <cutils/qtaguid.h>
 #include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <inttypes.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-
-#include "core_jni_helpers.h"
 #include <jni.h>
 #include <nativehelper/ScopedUtfChars.h>
-#include <utils/misc.h>
+#include <sys/stat.h>
+#include <sys/types.h>
 #include <utils/Log.h>
+#include <utils/misc.h>
 
-#include "android-base/unique_fd.h"
 #include "bpf/BpfUtils.h"
 #include "netdbpf/BpfNetworkStats.h"
 
@@ -104,10 +102,15 @@
     }
 }
 
+static int deleteTagData(JNIEnv* /* env */, jclass /* clazz */, jint uid) {
+    return qtaguid_deleteTagData(0, uid);
+}
+
 static const JNINativeMethod gMethods[] = {
         {"nativeGetTotalStat", "(I)J", (void*)getTotalStat},
         {"nativeGetIfaceStat", "(Ljava/lang/String;I)J", (void*)getIfaceStat},
         {"nativeGetUidStat", "(II)J", (void*)getUidStat},
+        {"nativeDeleteTagData", "(I)I", (void*)deleteTagData},
 };
 
 int register_android_server_net_NetworkStatsService(JNIEnv* env) {
diff --git a/services/core/jni/gnss/AGnssRil.cpp b/services/core/jni/gnss/AGnssRil.cpp
index d760b4d..424ffd4 100644
--- a/services/core/jni/gnss/AGnssRil.cpp
+++ b/services/core/jni/gnss/AGnssRil.cpp
@@ -41,7 +41,7 @@
 jboolean AGnssRil::setSetId(jint type, const jstring& setid_string) {
     JNIEnv* env = getJniEnv();
     ScopedJniString jniSetId{env, setid_string};
-    auto status = mIAGnssRil->setSetId((IAGnssRil::SetIDType)type, jniSetId.c_str());
+    auto status = mIAGnssRil->setSetId((IAGnssRil::SetIdType)type, jniSetId.c_str());
     return checkAidlStatus(status, "IAGnssRilAidl setSetId() failed.");
 }
 
diff --git a/services/core/jni/gnss/GnssMeasurementCallback.cpp b/services/core/jni/gnss/GnssMeasurementCallback.cpp
index fbdeec6..6c0d5d9 100644
--- a/services/core/jni/gnss/GnssMeasurementCallback.cpp
+++ b/services/core/jni/gnss/GnssMeasurementCallback.cpp
@@ -359,9 +359,7 @@
     jobjectArray measurementArray = translateAllGnssMeasurements(env, data.measurements);
 
     jobjectArray gnssAgcArray = nullptr;
-    if (data.gnssAgcs.has_value()) {
-        gnssAgcArray = translateAllGnssAgcs(env, data.gnssAgcs.value());
-    }
+    gnssAgcArray = translateAllGnssAgcs(env, data.gnssAgcs);
     setMeasurementData(env, mCallbacksObj, clock, measurementArray, gnssAgcArray);
 
     env->DeleteLocalRef(clock);
@@ -508,8 +506,8 @@
     return gnssMeasurementArray;
 }
 
-jobjectArray GnssMeasurementCallbackAidl::translateAllGnssAgcs(
-        JNIEnv* env, const std::vector<std::optional<GnssAgc>>& agcs) {
+jobjectArray GnssMeasurementCallbackAidl::translateAllGnssAgcs(JNIEnv* env,
+                                                               const std::vector<GnssAgc>& agcs) {
     if (agcs.size() == 0) {
         return nullptr;
     }
@@ -518,10 +516,7 @@
             env->NewObjectArray(agcs.size(), class_gnssAgc, nullptr /* initialElement */);
 
     for (uint16_t i = 0; i < agcs.size(); ++i) {
-        if (!agcs[i].has_value()) {
-            continue;
-        }
-        const GnssAgc& gnssAgc = agcs[i].value();
+        const GnssAgc& gnssAgc = agcs[i];
 
         jobject agcBuilderObject = env->NewObject(class_gnssAgcBuilder, method_gnssAgcBuilderCtor);
         env->CallObjectMethod(agcBuilderObject, method_gnssAgcBuilderSetLevelDb,
diff --git a/services/core/jni/gnss/GnssMeasurementCallback.h b/services/core/jni/gnss/GnssMeasurementCallback.h
index 9b346312..17af949 100644
--- a/services/core/jni/gnss/GnssMeasurementCallback.h
+++ b/services/core/jni/gnss/GnssMeasurementCallback.h
@@ -62,8 +62,8 @@
 
     jobjectArray translateAllGnssMeasurements(
             JNIEnv* env, const std::vector<hardware::gnss::GnssMeasurement>& measurements);
-    jobjectArray translateAllGnssAgcs(
-            JNIEnv* env, const std::vector<std::optional<hardware::gnss::GnssData::GnssAgc>>& agcs);
+    jobjectArray translateAllGnssAgcs(JNIEnv* env,
+                                      const std::vector<hardware::gnss::GnssData::GnssAgc>& agcs);
 
     void translateAndSetGnssData(const hardware::gnss::GnssData& data);
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 733cfcd..3bda7bf0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -56,6 +56,8 @@
 import static android.app.admin.DevicePolicyManager.DELEGATION_PACKAGE_ACCESS;
 import static android.app.admin.DevicePolicyManager.DELEGATION_PERMISSION_GRANT;
 import static android.app.admin.DevicePolicyManager.DELEGATION_SECURITY_LOGGING;
+import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT;
+import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
 import static android.app.admin.DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER;
 import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_ID;
 import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE_DRAWABLE;
@@ -66,9 +68,12 @@
 import static android.app.admin.DevicePolicyManager.ID_TYPE_MEID;
 import static android.app.admin.DevicePolicyManager.ID_TYPE_SERIAL;
 import static android.app.admin.DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
 import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD;
 import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS;
 import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_SYSTEM_INFO;
 import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY;
 import static android.app.admin.DevicePolicyManager.NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
 import static android.app.admin.DevicePolicyManager.OPERATION_SAFETY_REASON_NONE;
@@ -3918,8 +3923,8 @@
 
         final CallerIdentity caller = getCallerIdentity(who);
         Preconditions.checkCallAuthorization(
-                isProfileOwner(caller) || isDeviceOwner(caller) || isSystemUid(caller)
-                || isPasswordLimitingAdminTargetingP(caller));
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller)
+                        || isSystemUid(caller) || isPasswordLimitingAdminTargetingP(caller));
 
         if (parent) {
             Preconditions.checkCallAuthorization(
@@ -4772,7 +4777,8 @@
         Objects.requireNonNull(admin, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+                || isProfileOwner(caller));
         Preconditions.checkCallingUser(isManagedProfile(caller.getUserId()));
 
         return !isSeparateProfileChallengeEnabled(caller.getUserId());
@@ -4860,12 +4866,12 @@
         enforceUserUnlocked(caller.getUserId());
         if (parent) {
             Preconditions.checkCallAuthorization(
-                    isDeviceOwner(caller) || isProfileOwner(caller) || isSystemUid(caller),
+                    isDefaultDeviceOwner(caller) || isProfileOwner(caller) || isSystemUid(caller),
                     "Only profile owner, device owner and system may call this method on parent.");
         } else {
             Preconditions.checkCallAuthorization(
                     hasCallingOrSelfPermission(REQUEST_PASSWORD_COMPLEXITY)
-                            || isDeviceOwner(caller) || isProfileOwner(caller),
+                            || isDefaultDeviceOwner(caller) || isProfileOwner(caller),
                     "Must have " + REQUEST_PASSWORD_COMPLEXITY
                             + " permission, or be a profile owner or device owner.");
         }
@@ -4888,7 +4894,8 @@
                 "Provided complexity is not one of the allowed values.");
 
         final CallerIdentity caller = getCallerIdentity();
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
         Preconditions.checkArgument(!calledOnParent || isProfileOwner(caller));
 
         synchronized (getLockObject()) {
@@ -4968,7 +4975,7 @@
 
         final CallerIdentity caller = getCallerIdentity();
         Preconditions.checkCallAuthorization(
-                isDeviceOwner(caller) || isProfileOwner(caller));
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
 
         Preconditions.checkArgument(!calledOnParent || isProfileOwner(caller));
 
@@ -5160,7 +5167,7 @@
         }
 
         // If caller has PO (or DO) throw or fail silently depending on its target SDK level.
-        if (isDeviceOwner(caller) || isProfileOwner(caller)) {
+        if (isDefaultDeviceOwner(caller) || isProfileOwner(caller)) {
             synchronized (getLockObject()) {
                 ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
                 if (getTargetSdk(admin.info.getPackageName(), userHandle) < Build.VERSION_CODES.O) {
@@ -5219,7 +5226,7 @@
             return false;
         }
 
-        boolean callerIsDeviceOwnerAdmin = isDeviceOwner(caller);
+        boolean callerIsDeviceOwnerAdmin = isDefaultDeviceOwner(caller);
         boolean doNotAskCredentialsOnBoot =
                 (flags & DevicePolicyManager.RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT) != 0;
         if (callerIsDeviceOwnerAdmin && doNotAskCredentialsOnBoot) {
@@ -5406,7 +5413,8 @@
         Objects.requireNonNull(who, "ComponentName is null");
         Preconditions.checkArgument(timeoutMs >= 0, "Timeout must not be a negative number.");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
         // timeoutMs with value 0 means that the admin doesn't participate
         // timeoutMs is clamped to the interval in case the internal constants change in the future
         final long minimumStrongAuthTimeout = getMinimumStrongAuthTimeoutMs();
@@ -5575,7 +5583,8 @@
     }
 
     private boolean canManageCaCerts(CallerIdentity caller) {
-        return (caller.hasAdminComponent() && (isDeviceOwner(caller) || isProfileOwner(caller)))
+        return (caller.hasAdminComponent() && (isDefaultDeviceOwner(caller)
+                || isProfileOwner(caller)))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_CERT_INSTALL))
                 || hasCallingOrSelfPermission(MANAGE_CA_CERTIFICATES);
     }
@@ -5689,7 +5698,7 @@
         final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
         final boolean isCredentialManagementApp = isCredentialManagementApp(caller);
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                && (isProfileOwner(caller) || isDeviceOwner(caller)))
+                && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
                 || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
         if (isCredentialManagementApp) {
             Preconditions.checkCallAuthorization(!isUserSelectable, "The credential "
@@ -5754,7 +5763,7 @@
         final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
         final boolean isCredentialManagementApp = isCredentialManagementApp(caller);
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                && (isProfileOwner(caller) || isDeviceOwner(caller)))
+                && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
                 || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
         if (isCredentialManagementApp) {
             Preconditions.checkCallAuthorization(
@@ -5818,12 +5827,12 @@
     }
 
     private boolean canInstallCertificates(CallerIdentity caller) {
-        return isProfileOwner(caller) || isDeviceOwner(caller)
+        return isProfileOwner(caller) || isDefaultDeviceOwner(caller)
                 || isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
     }
 
     private boolean canChooseCertificates(CallerIdentity caller) {
-        return isProfileOwner(caller) || isDeviceOwner(caller)
+        return isProfileOwner(caller) || isDefaultDeviceOwner(caller)
                 || isCallerDelegate(caller, DELEGATION_CERT_SELECTION);
     }
 
@@ -5871,7 +5880,7 @@
 
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                && (isProfileOwner(caller) || isDeviceOwner(caller)))
+                && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_CERT_SELECTION)));
 
         final int granteeUid;
@@ -5984,7 +5993,7 @@
 
         // If not, fall back to the device owner check.
         Preconditions.checkCallAuthorization(
-                isDeviceOwner(caller) || isCallerDelegate(caller, DELEGATION_CERT_INSTALL));
+                isDefaultDeviceOwner(caller) || isCallerDelegate(caller, DELEGATION_CERT_INSTALL));
     }
 
     @VisibleForTesting
@@ -6047,7 +6056,7 @@
             enforceIndividualAttestationSupportedIfRequested(attestationUtilsFlags);
         } else {
             Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                    && (isProfileOwner(caller) || isDeviceOwner(caller)))
+                    && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
                     || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
             if (isCredentialManagementApp) {
                 Preconditions.checkCallAuthorization(
@@ -6182,7 +6191,7 @@
         final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
         final boolean isCredentialManagementApp = isCredentialManagementApp(caller);
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                && (isProfileOwner(caller) || isDeviceOwner(caller)))
+                && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
                 || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
         if (isCredentialManagementApp) {
             Preconditions.checkCallAuthorization(
@@ -6337,14 +6346,15 @@
         final int userId = caller.getUserId();
         // Ensure calling process is device/profile owner.
         if (!Collections.disjoint(scopes, DEVICE_OWNER_OR_MANAGED_PROFILE_OWNER_DELEGATIONS)) {
-            Preconditions.checkCallAuthorization(isDeviceOwner(caller)
+            Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
                     || (isProfileOwner(caller) && isManagedProfile(caller.getUserId())));
         } else if (!Collections.disjoint(
                 scopes, DEVICE_OWNER_OR_ORGANIZATION_OWNED_MANAGED_PROFILE_OWNER_DELEGATIONS)) {
-            Preconditions.checkCallAuthorization(isDeviceOwner(caller)
+            Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
                     || isProfileOwnerOfOrganizationOwnedDevice(caller));
         } else {
-            Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+            Preconditions.checkCallAuthorization(
+                    isDefaultDeviceOwner(caller) || isProfileOwner(caller));
         }
 
         synchronized (getLockObject()) {
@@ -6434,7 +6444,8 @@
         // * Either it's a profile owner / device owner, if componentName is provided
         // * Or it's an app querying its own delegation scopes
         if (caller.hasAdminComponent()) {
-            Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+            Preconditions.checkCallAuthorization(
+                    isProfileOwner(caller) || isDefaultDeviceOwner(caller));
         } else {
             Preconditions.checkCallAuthorization(isPackage(caller, delegatePackage),
                     String.format("Caller with uid %d is not %s", caller.getUid(),
@@ -6467,7 +6478,8 @@
 
         // Retrieve the user ID of the calling process.
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
         synchronized (getLockObject()) {
             return getDelegatePackagesInternalLocked(scope, caller.getUserId());
         }
@@ -6600,7 +6612,8 @@
 
         final CallerIdentity caller = getCallerIdentity(who);
         // Ensure calling process is device/profile owner.
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
 
         synchronized (getLockObject()) {
             final DevicePolicyData policy = getUserData(caller.getUserId());
@@ -6712,7 +6725,8 @@
         Objects.requireNonNull(who, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_ALWAYS_ON_VPN_PACKAGE);
 
         if (vpnPackage == null) {
@@ -6792,7 +6806,8 @@
         Objects.requireNonNull(admin, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
 
         return mInjector.binderWithCleanCallingIdentity(
                 () -> mInjector.getVpnManager().getAlwaysOnVpnPackageForUser(caller.getUserId()));
@@ -6818,7 +6833,8 @@
             caller = getCallerIdentity();
         } else {
             caller = getCallerIdentity(admin);
-            Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+            Preconditions.checkCallAuthorization(
+                    isDefaultDeviceOwner(caller) || isProfileOwner(caller));
         }
 
         return mInjector.binderWithCleanCallingIdentity(
@@ -6841,7 +6857,8 @@
         Objects.requireNonNull(admin, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
 
         return mInjector.binderWithCleanCallingIdentity(
                 () -> mInjector.getVpnManager().getVpnLockdownAllowlist(caller.getUserId()));
@@ -6946,8 +6963,9 @@
                             + "organization-owned device.");
         }
         if ((flags & WIPE_RESET_PROTECTION_DATA) != 0) {
-            Preconditions.checkCallAuthorization(isDeviceOwner(caller)
-                            || calledByProfileOwnerOnOrgOwnedDevice,
+            Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+                            || calledByProfileOwnerOnOrgOwnedDevice
+                            || isFinancedDeviceOwner(caller),
                     "Only device owners or profile owners of organization-owned device can set "
                             + "WIPE_RESET_PROTECTION_DATA");
         }
@@ -7139,8 +7157,8 @@
         }
         Preconditions.checkNotNull(who, "ComponentName is null");
         CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller)
-                || isProfileOwnerOfOrganizationOwnedDevice(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager
                 .OPERATION_SET_FACTORY_RESET_PROTECTION_POLICY);
 
@@ -7189,7 +7207,8 @@
                         UserHandle.getUserId(frpManagementAgentUid));
             } else {
                 Preconditions.checkCallAuthorization(
-                        isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
+                        isDefaultDeviceOwner(caller)
+                                || isProfileOwnerOfOrganizationOwnedDevice(caller));
                 admin = getProfileOwnerOrDeviceOwnerLocked(caller);
             }
         }
@@ -7617,7 +7636,7 @@
     public void setRecommendedGlobalProxy(ComponentName who, ProxyInfo proxyInfo) {
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
         checkAllUsersAreAffiliatedWithDevice();
         mInjector.binderWithCleanCallingIdentity(
                 () -> mInjector.getConnectivityManager().setGlobalProxy(proxyInfo));
@@ -7915,7 +7934,8 @@
         }
 
         final CallerIdentity caller = getCallerIdentity();
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
 
         synchronized (getLockObject()) {
             final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
@@ -7934,8 +7954,7 @@
 
         final CallerIdentity caller = getCallerIdentity();
         Preconditions.checkCallAuthorization(
-                isProfileOwner(caller)
-                        || isDeviceOwner(caller)
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller)
                         || hasCallingOrSelfPermission(permission.READ_NEARBY_STREAMING_POLICY));
 
         synchronized (getLockObject()) {
@@ -7955,7 +7974,8 @@
         }
 
         final CallerIdentity caller = getCallerIdentity();
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
 
         synchronized (getLockObject()) {
             final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
@@ -7974,8 +7994,7 @@
 
         final CallerIdentity caller = getCallerIdentity();
         Preconditions.checkCallAuthorization(
-                isProfileOwner(caller)
-                        || isDeviceOwner(caller)
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller)
                         || hasCallingOrSelfPermission(permission.READ_NEARBY_STREAMING_POLICY));
 
         synchronized (getLockObject()) {
@@ -8067,7 +8086,7 @@
 
         final CallerIdentity caller = getCallerIdentity(who);
         Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
-                || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDeviceOwner(caller));
+                || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller));
 
         mInjector.binderWithCleanCallingIdentity(() ->
                 mInjector.settingsGlobalPutInt(Settings.Global.AUTO_TIME, enabled ? 1 : 0));
@@ -8091,7 +8110,7 @@
 
         final CallerIdentity caller = getCallerIdentity(who);
         Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
-                || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDeviceOwner(caller));
+                || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller));
 
         return mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) > 0;
     }
@@ -8108,7 +8127,7 @@
 
         final CallerIdentity caller = getCallerIdentity(who);
         Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
-                || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDeviceOwner(caller));
+                || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller));
 
         mInjector.binderWithCleanCallingIdentity(() ->
                 mInjector.settingsGlobalPutInt(Global.AUTO_TIME_ZONE, enabled ? 1 : 0));
@@ -8132,7 +8151,7 @@
 
         final CallerIdentity caller = getCallerIdentity(who);
         Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
-                || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDeviceOwner(caller));
+                || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller));
 
         return mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) > 0;
     }
@@ -8161,7 +8180,7 @@
         // which could still contain data related to that user. Should we disallow that, e.g. until
         // next boot? Might not be needed given that this still requires user consent.
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
         checkAllUsersAreAffiliatedWithDevice();
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_REQUEST_BUGREPORT);
 
@@ -8472,7 +8491,8 @@
         }
         Objects.requireNonNull(packageList, "packageList is null");
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
-        Preconditions.checkCallAuthorization((caller.hasAdminComponent() &&  isDeviceOwner(caller))
+        Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+                &&  isDefaultDeviceOwner(caller))
                 || (caller.hasPackage()
                 && isCallerDelegate(caller, DELEGATION_KEEP_UNINSTALLED_PACKAGES)));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_KEEP_UNINSTALLED_PACKAGES);
@@ -8501,7 +8521,8 @@
             return null;
         }
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
-        Preconditions.checkCallAuthorization((caller.hasAdminComponent() &&  isDeviceOwner(caller))
+        Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+                &&  isDefaultDeviceOwner(caller))
                 || (caller.hasPackage()
                 && isCallerDelegate(caller, DELEGATION_KEEP_UNINSTALLED_PACKAGES)));
 
@@ -8615,8 +8636,8 @@
     @Override
     public boolean hasDeviceOwner() {
         final CallerIdentity caller = getCallerIdentity();
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller)
-                        || canManageUsers(caller)
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+                        || canManageUsers(caller) || isFinancedDeviceOwner(caller)
                         || hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
         return mOwners.hasDeviceOwner();
     }
@@ -8633,17 +8654,29 @@
         }
     }
 
-    private boolean isDeviceOwner(CallerIdentity caller) {
+    /**
+     * Returns {@code true} <b>only if</b> the caller is the device owner and the device owner type
+     * is {@link DevicePolicyManager#DEVICE_OWNER_TYPE_DEFAULT}. {@code false} is returned for the
+     * case where the caller is not the device owner, there is no device owner, or the device owner
+     * type is not {@link DevicePolicyManager#DEVICE_OWNER_TYPE_DEFAULT}.
+     *
+     */
+    private boolean isDefaultDeviceOwner(CallerIdentity caller) {
         synchronized (getLockObject()) {
-            if (!mOwners.hasDeviceOwner() || mOwners.getDeviceOwnerUserId() != caller.getUserId()) {
-                return false;
-            }
+            return isDeviceOwnerLocked(caller) && getDeviceOwnerTypeLocked(
+                    mOwners.getDeviceOwnerPackageName()) == DEVICE_OWNER_TYPE_DEFAULT;
+        }
+    }
 
-            if (caller.hasAdminComponent()) {
-                return mOwners.getDeviceOwnerComponent().equals(caller.getComponentName());
-            } else {
-                return isUidDeviceOwnerLocked(caller.getUid());
-            }
+    private boolean isDeviceOwnerLocked(CallerIdentity caller) {
+        if (!mOwners.hasDeviceOwner() || mOwners.getDeviceOwnerUserId() != caller.getUserId()) {
+            return false;
+        }
+
+        if (caller.hasAdminComponent()) {
+            return mOwners.getDeviceOwnerComponent().equals(caller.getComponentName());
+        } else {
+            return isUidDeviceOwnerLocked(caller.getUid());
         }
     }
 
@@ -9073,8 +9106,8 @@
         Objects.requireNonNull(who, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller)
-                || isProfileOwnerOfOrganizationOwnedDevice(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
 
         mInjector.binderWithCleanCallingIdentity(() ->
                 mLockPatternUtils.setDeviceOwnerInfo(info != null ? info.toString() : null));
@@ -9258,7 +9291,8 @@
 
         final CallerIdentity caller = getCallerIdentity(who);
         final int userId = caller.getUserId();
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
         Preconditions.checkCallingUser(isManagedProfile(userId));
 
         synchronized (getLockObject()) {
@@ -9289,7 +9323,8 @@
         Objects.requireNonNull(who, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
 
         mInjector.binderWithCleanCallingIdentity(() -> {
             mUserManager.setUserName(caller.getUserId(), profileName);
@@ -9725,7 +9760,8 @@
     }
 
     private void enforceCanCallLockTaskLocked(CallerIdentity caller) {
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isProfileOwner(caller)
+                || isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller));
 
         final int userId =  caller.getUserId();
         if (!canUserUseLockTaskLocked(userId)) {
@@ -9982,7 +10018,8 @@
             ComponentName activity) {
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isProfileOwner(caller)
+                || isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller));
 
         final int userHandle = caller.getUserId();
         synchronized (getLockObject()) {
@@ -10009,7 +10046,8 @@
     public void clearPackagePersistentPreferredActivities(ComponentName who, String packageName) {
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isProfileOwner(caller)
+                || isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller));
 
         final int userHandle = caller.getUserId();
         synchronized (getLockObject()) {
@@ -10030,7 +10068,7 @@
         Objects.requireNonNull(admin, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller)
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
                 || (parent && isProfileOwnerOfOrganizationOwnedDevice(caller)));
         if (parent) {
             mInjector.binderWithCleanCallingIdentity(() -> enforcePackageIsSystemPackage(
@@ -10070,7 +10108,7 @@
             String packageName, Bundle settings) {
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                && (isProfileOwner(caller) || isDeviceOwner(caller)))
+                && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS)));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_APPLICATION_RESTRICTIONS);
 
@@ -10168,7 +10206,8 @@
     public void setRestrictionsProvider(ComponentName who, ComponentName permissionProvider) {
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_RESTRICTIONS_PROVIDER);
 
         synchronized (getLockObject()) {
@@ -10193,7 +10232,8 @@
     public void addCrossProfileIntentFilter(ComponentName who, IntentFilter filter, int flags) {
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
         int callingUserId = caller.getUserId();
         synchronized (getLockObject()) {
             long id = mInjector.binderClearCallingIdentity();
@@ -10242,7 +10282,8 @@
     public void clearCrossProfileIntentFilters(ComponentName who) {
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
 
         int callingUserId = caller.getUserId();
         synchronized (getLockObject()) {
@@ -10382,7 +10423,8 @@
         }
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
 
         synchronized (getLockObject()) {
             ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
@@ -10495,7 +10537,8 @@
                             + "system input methods when called on the parent instance of an "
                             + "organization-owned device");
         } else {
-            Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+            Preconditions.checkCallAuthorization(
+                    isDefaultDeviceOwner(caller) || isProfileOwner(caller));
         }
 
         if (packageList != null) {
@@ -10553,7 +10596,8 @@
         if (calledOnParentInstance) {
             Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller));
         } else {
-            Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+            Preconditions.checkCallAuthorization(
+                    isDefaultDeviceOwner(caller) || isProfileOwner(caller));
         }
 
         synchronized (getLockObject()) {
@@ -10732,7 +10776,7 @@
         // Only allow the system user to use this method
         Preconditions.checkCallAuthorization(caller.getUserHandle().isSystem(),
                 "createAndManageUser was called from non-system user");
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_CREATE_AND_MANAGE_USER);
 
         final boolean ephemeral = (flags & DevicePolicyManager.MAKE_USER_EPHEMERAL) != 0;
@@ -10974,7 +11018,7 @@
         Objects.requireNonNull(who, "ComponentName is null");
         Objects.requireNonNull(userHandle, "UserHandle is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_REMOVE_USER);
 
         return mInjector.binderWithCleanCallingIdentity(() -> {
@@ -11008,7 +11052,7 @@
     public boolean switchUser(ComponentName who, UserHandle userHandle) {
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SWITCH_USER);
 
         boolean switched = false;
@@ -11083,7 +11127,7 @@
         Objects.requireNonNull(who, "ComponentName is null");
         Objects.requireNonNull(userHandle, "UserHandle is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_START_USER_IN_BACKGROUND);
 
         final int userId = userHandle.getIdentifier();
@@ -11119,7 +11163,7 @@
         Objects.requireNonNull(who, "ComponentName is null");
         Objects.requireNonNull(userHandle, "UserHandle is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_STOP_USER);
 
         final int userId = userHandle.getIdentifier();
@@ -11135,7 +11179,8 @@
     public int logoutUser(ComponentName who) {
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_LOGOUT_USER);
 
         final int callingUserId = caller.getUserId();
@@ -11224,7 +11269,7 @@
     public List<UserHandle> getSecondaryUsers(ComponentName who) {
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
 
         return mInjector.binderWithCleanCallingIdentity(() -> {
             final List<UserInfo> userInfos = mInjector.getUserManager().getAliveUsers();
@@ -11244,7 +11289,8 @@
         Objects.requireNonNull(who, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
 
         return mInjector.binderWithCleanCallingIdentity(
                 () -> mInjector.getUserManager().isUserEphemeral(caller.getUserId()));
@@ -11255,7 +11301,7 @@
             String packageName) {
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                && (isProfileOwner(caller) || isDeviceOwner(caller)))
+                && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS)));
 
         return mInjector.binderWithCleanCallingIdentity(() -> {
@@ -11306,7 +11352,7 @@
         Objects.requireNonNull(packageNames, "array of packages cannot be null");
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                && (isProfileOwner(caller) || isDeviceOwner(caller)))
+                && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS)));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_PACKAGES_SUSPENDED);
 
@@ -11369,7 +11415,7 @@
     public boolean isPackageSuspended(ComponentName who, String callerPackage, String packageName) {
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                && (isProfileOwner(caller) || isDeviceOwner(caller)))
+                && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS)));
 
         synchronized (getLockObject()) {
@@ -11390,8 +11436,8 @@
     public List<String> listPolicyExemptApps() {
         CallerIdentity caller = getCallerIdentity();
         Preconditions.checkCallAuthorization(
-                hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS) || isDeviceOwner(caller)
-                        || isProfileOwner(caller));
+                hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS)
+                        || isDefaultDeviceOwner(caller) || isProfileOwner(caller));
 
         return listPolicyExemptAppsUnchecked();
     }
@@ -11432,12 +11478,19 @@
             final ActiveAdmin activeAdmin = getParentOfAdminIfRequired(
                     getProfileOwnerOrDeviceOwnerLocked(caller), parent);
 
-            if (isDeviceOwner(caller)) {
+            if (isDefaultDeviceOwner(caller)) {
                 if (!UserRestrictionsUtils.canDeviceOwnerChange(key)) {
                     throw new SecurityException("Device owner cannot set user restriction " + key);
                 }
                 Preconditions.checkArgument(!parent,
                         "Cannot use the parent instance in Device Owner mode");
+            } else if (isFinancedDeviceOwner(caller)) {
+                if (!UserRestrictionsUtils.canFinancedDeviceOwnerChange(key)) {
+                    throw new SecurityException("Cannot set user restriction " + key
+                            + " when managing a financed device");
+                }
+                Preconditions.checkArgument(!parent,
+                        "Cannot use the parent instance in Financed Device Owner mode");
             } else {
                 boolean profileOwnerCanChangeOnItself = !parent
                         && UserRestrictionsUtils.canProfileOwnerChange(key, userHandle);
@@ -11546,7 +11599,8 @@
         Objects.requireNonNull(who, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+                || isProfileOwner(caller)
                 || (parent && isProfileOwnerOfOrganizationOwnedDevice(caller)));
 
         synchronized (getLockObject()) {
@@ -11561,7 +11615,7 @@
             boolean hidden, boolean parent) {
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                && (isProfileOwner(caller) || isDeviceOwner(caller)))
+                && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS)));
 
         List<String> exemptApps = listPolicyExemptAppsUnchecked();
@@ -11607,7 +11661,7 @@
             String packageName, boolean parent) {
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                && (isProfileOwner(caller) || isDeviceOwner(caller)))
+                && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS)));
 
         final int userId = parent ? getProfileParentId(caller.getUserId()) : caller.getUserId();
@@ -11643,7 +11697,7 @@
     public void enableSystemApp(ComponentName who, String callerPackage, String packageName) {
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                && (isProfileOwner(caller) || isDeviceOwner(caller)))
+                && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_ENABLE_SYSTEM_APP)));
 
         synchronized (getLockObject()) {
@@ -11687,7 +11741,7 @@
     public int enableSystemAppWithIntent(ComponentName who, String callerPackage, Intent intent) {
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                && (isProfileOwner(caller) || isDeviceOwner(caller)))
+                && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_ENABLE_SYSTEM_APP)));
 
         int numberOfAppsInstalled = 0;
@@ -11756,7 +11810,7 @@
             String packageName) {
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                && (isProfileOwner(caller) || isDeviceOwner(caller)))
+                && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
                 || (caller.hasPackage()
                 && isCallerDelegate(caller, DELEGATION_INSTALL_EXISTING_PACKAGE)));
 
@@ -11872,7 +11926,8 @@
             boolean uninstallBlocked) {
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                && (isProfileOwner(caller) || isDeviceOwner(caller)))
+                && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)
+                || isFinancedDeviceOwner(caller)))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_BLOCK_UNINSTALL)));
 
         final int userId = caller.getUserId();
@@ -11913,7 +11968,8 @@
             if (who != null) {
                 final CallerIdentity caller = getCallerIdentity(who);
                 Preconditions.checkCallAuthorization(
-                        isProfileOwner(caller) || isDeviceOwner(caller));
+                        isProfileOwner(caller) || isDefaultDeviceOwner(caller)
+                                || isFinancedDeviceOwner(caller));
             }
             try {
                 return mIPackageManager.getBlockUninstallForUser(packageName, userId);
@@ -12087,7 +12143,8 @@
         Objects.requireNonNull(who, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
 
         synchronized (getLockObject()) {
             ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
@@ -12111,7 +12168,8 @@
         Objects.requireNonNull(who, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
 
         synchronized (getLockObject()) {
             ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
@@ -12136,7 +12194,8 @@
 
         // Check can set secondary lockscreen enabled
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
         Preconditions.checkCallAuthorization(!isManagedProfile(caller.getUserId()),
                 "User %d is not allowed to call setSecondaryLockscreenEnabled",
                         caller.getUserId());
@@ -12325,6 +12384,7 @@
         final int userHandle = caller.getUserId();
         synchronized (getLockObject()) {
             enforceCanCallLockTaskLocked(caller);
+            enforceCanSetLockTaskFeaturesOnFinancedDevice(caller, flags);
             checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_LOCK_TASK_FEATURES);
             setLockTaskFeaturesLocked(userHandle, flags);
         }
@@ -12373,6 +12433,24 @@
         });
     }
 
+    private void enforceCanSetLockTaskFeaturesOnFinancedDevice(CallerIdentity caller, int flags) {
+        int allowedFlags = LOCK_TASK_FEATURE_SYSTEM_INFO | LOCK_TASK_FEATURE_KEYGUARD
+                | LOCK_TASK_FEATURE_HOME | LOCK_TASK_FEATURE_GLOBAL_ACTIONS
+                | LOCK_TASK_FEATURE_NOTIFICATIONS;
+
+        if (!isFinancedDeviceOwner(caller)) {
+            return;
+        }
+
+        if ((flags == 0) || ((flags & ~(allowedFlags)) != 0)) {
+            throw new SecurityException(
+                    "Permitted lock task features when managing a financed device: "
+                            + "LOCK_TASK_FEATURE_SYSTEM_INFO, LOCK_TASK_FEATURE_KEYGUARD, "
+                            + "LOCK_TASK_FEATURE_HOME, LOCK_TASK_FEATURE_GLOBAL_ACTIONS, "
+                            + "or LOCK_TASK_FEATURE_NOTIFICATIONS");
+        }
+    }
+
     @Override
     public void notifyLockTaskModeChanged(boolean isEnabled, String pkg, int userHandle) {
         Preconditions.checkCallAuthorization(isSystemUid(getCallerIdentity()),
@@ -12413,7 +12491,7 @@
     public void setGlobalSetting(ComponentName who, String setting, String value) {
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
 
         DevicePolicyEventLogger
                 .createEvent(DevicePolicyEnums.SET_GLOBAL_SETTING)
@@ -12454,7 +12532,8 @@
         Objects.requireNonNull(who, "ComponentName is null");
         Preconditions.checkStringNotEmpty(setting, "String setting is null or empty");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_SYSTEM_SETTING);
 
         synchronized (getLockObject()) {
@@ -12476,8 +12555,8 @@
         Preconditions.checkNotNull(who, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller)
-                || isProfileOwnerOfOrganizationOwnedDevice(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
 
         mInjector.binderWithCleanCallingIdentity(() ->
                 mInjector.settingsGlobalPutInt(Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN,
@@ -12498,8 +12577,8 @@
         Preconditions.checkNotNull(who, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller)
-                || isProfileOwnerOfOrganizationOwnedDevice(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
 
         return mInjector.binderWithCleanCallingIdentity(() ->
                 mInjector.settingsGlobalGetInt(Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) > 0);
@@ -12510,7 +12589,7 @@
         Preconditions.checkNotNull(who, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
 
         UserHandle userHandle = caller.getUserHandle();
         if (mIsAutomotive && !locationEnabled) {
@@ -12592,8 +12671,8 @@
         Objects.requireNonNull(who, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller)
-                || isProfileOwnerOfOrganizationOwnedDevice(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
 
         // Don't allow set time when auto time is on.
         if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) == 1) {
@@ -12612,8 +12691,8 @@
         Objects.requireNonNull(who, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller)
-                || isProfileOwnerOfOrganizationOwnedDevice(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
 
         // Don't allow set timezone when auto timezone is on.
         if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) == 1) {
@@ -12633,7 +12712,8 @@
     public void setSecureSetting(ComponentName who, String setting, String value) {
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
 
         int callingUserId = caller.getUserId();
         synchronized (getLockObject()) {
@@ -12720,7 +12800,8 @@
     public void setMasterVolumeMuted(ComponentName who, boolean on) {
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_MASTER_VOLUME_MUTED);
 
         synchronized (getLockObject()) {
@@ -12737,7 +12818,8 @@
     public boolean isMasterVolumeMuted(ComponentName who) {
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
 
         synchronized (getLockObject()) {
             AudioManager audioManager =
@@ -12750,7 +12832,8 @@
     public void setUserIcon(ComponentName who, Bitmap icon) {
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
 
         synchronized (getLockObject()) {
             mInjector.binderWithCleanCallingIdentity(
@@ -12766,7 +12849,8 @@
     public boolean setKeyguardDisabled(ComponentName who, boolean disabled) {
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
 
         final int userId = caller.getUserId();
         synchronized (getLockObject()) {
@@ -12808,7 +12892,8 @@
     @Override
     public boolean setStatusBarDisabled(ComponentName who, boolean disabled) {
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
 
         int userId = caller.getUserId();
         synchronized (getLockObject()) {
@@ -13042,7 +13127,7 @@
 
         @Override
         public boolean isActiveDeviceOwner(int uid) {
-            return isDeviceOwner(new CallerIdentity(uid, null, null));
+            return isDefaultDeviceOwner(new CallerIdentity(uid, null, null));
         }
 
         @Override
@@ -13633,7 +13718,7 @@
 
         synchronized (getLockObject()) {
             Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller)
-                    || isDeviceOwner(caller));
+                    || isDefaultDeviceOwner(caller));
             checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_SYSTEM_UPDATE_POLICY);
 
             if (policy == null) {
@@ -13831,7 +13916,8 @@
         Objects.requireNonNull(admin, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
 
         return mOwners.getSystemUpdateInfo();
     }
@@ -13840,7 +13926,7 @@
     public void setPermissionPolicy(ComponentName admin, String callerPackage, int policy) {
         final CallerIdentity caller = getCallerIdentity(admin, callerPackage);
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                && (isProfileOwner(caller) || isDeviceOwner(caller)))
+                && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PERMISSION_GRANT)));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_PERMISSION_POLICY);
 
@@ -13882,11 +13968,15 @@
 
         final CallerIdentity caller = getCallerIdentity(admin, callerPackage);
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                && (isProfileOwner(caller) || isDeviceOwner(caller)))
+                && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)
+                || isFinancedDeviceOwner(caller)))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PERMISSION_GRANT)));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_PERMISSION_GRANT_STATE);
 
         synchronized (getLockObject()) {
+            if (isFinancedDeviceOwner(caller)) {
+                enforceCanSetPermissionGrantOnFinancedDevice(packageName, permission);
+            }
             long ident = mInjector.binderClearCallingIdentity();
             try {
                 boolean isPostQAdmin = getTargetSdk(caller.getPackageName(), caller.getUserId())
@@ -13946,12 +14036,23 @@
         }
     }
 
+    private void enforceCanSetPermissionGrantOnFinancedDevice(
+            String packageName, String permission) {
+        if (!Manifest.permission.READ_PHONE_STATE.equals(permission)) {
+            throw new SecurityException("Cannot grant " + permission
+                    + " when managing a financed device");
+        } else if (!mOwners.getDeviceOwnerPackageName().equals(packageName)) {
+            throw new SecurityException("Cannot grant permission to a package that is not"
+                    + " the device owner");
+        }
+    }
+
     @Override
     public int getPermissionGrantState(ComponentName admin, String callerPackage,
             String packageName, String permission) throws RemoteException {
         final CallerIdentity caller = getCallerIdentity(admin, callerPackage);
         Preconditions.checkCallAuthorization(isSystemUid(caller) || (caller.hasAdminComponent()
-                && (isProfileOwner(caller) || isDeviceOwner(caller)))
+                && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PERMISSION_GRANT)));
 
         synchronized (getLockObject()) {
@@ -14231,7 +14332,7 @@
     }
 
     private void checkIsDeviceOwner(CallerIdentity caller) {
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller), caller.getUid()
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller), caller.getUid()
                 + " is not device owner");
     }
 
@@ -14263,8 +14364,8 @@
         Objects.requireNonNull(admin, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller)
-                || isProfileOwnerOfOrganizationOwnedDevice(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
 
         return mInjector.binderWithCleanCallingIdentity(() -> {
             String[] macAddresses = mInjector.getWifiManager().getFactoryMacAddresses();
@@ -14299,7 +14400,8 @@
         Objects.requireNonNull(admin, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
 
         return isManagedProfile(caller.getUserId());
     }
@@ -14308,7 +14410,7 @@
     public void reboot(ComponentName admin) {
         Objects.requireNonNull(admin, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_REBOOT);
         mInjector.binderWithCleanCallingIdentity(() -> {
             // Make sure there are no ongoing calls on the device.
@@ -14542,7 +14644,8 @@
             return null;
         }
         final CallerIdentity caller = getCallerIdentity();
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || canManageUsers(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+                || canManageUsers(caller) || isFinancedDeviceOwner(caller));
         synchronized (getLockObject()) {
             final ActiveAdmin deviceOwnerAdmin = getDeviceOwnerAdminLocked();
             return deviceOwnerAdmin == null ? null : deviceOwnerAdmin.organizationName;
@@ -14576,7 +14679,8 @@
         Objects.requireNonNull(who);
         Objects.requireNonNull(packageNames);
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller),
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller),
                 "Admin %s does not own the profile", caller.getComponentName());
 
         if (!mHasFeature) {
@@ -14627,7 +14731,8 @@
             return new ArrayList<>();
         }
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller),
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller),
                 "Admin %s does not own the profile", caller.getComponentName());
 
         synchronized (getLockObject()) {
@@ -14766,7 +14871,8 @@
 
         final Set<String> affiliationIds = new ArraySet<>(ids);
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
         final int callingUserId = caller.getUserId();
 
         synchronized (getLockObject()) {
@@ -14797,7 +14903,8 @@
 
         Objects.requireNonNull(admin);
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
 
         synchronized (getLockObject()) {
             return new ArrayList<String>(getUserData(caller.getUserId()).mAffiliationIds);
@@ -14891,7 +14998,7 @@
             if (admin != null) {
                 Preconditions.checkCallAuthorization(
                         isProfileOwnerOfOrganizationOwnedDevice(caller)
-                        || isDeviceOwner(caller));
+                        || isDefaultDeviceOwner(caller));
             } else {
                 // A delegate app passes a null admin component, which is expected
                 Preconditions.checkCallAuthorization(
@@ -14928,7 +15035,7 @@
                 if (admin != null) {
                     Preconditions.checkCallAuthorization(
                             isProfileOwnerOfOrganizationOwnedDevice(caller)
-                            || isDeviceOwner(caller));
+                            || isDefaultDeviceOwner(caller));
                 } else {
                     // A delegate app passes a null admin component, which is expected
                     Preconditions.checkCallAuthorization(
@@ -14961,7 +15068,7 @@
         if (admin != null) {
             Preconditions.checkCallAuthorization(
                     isProfileOwnerOfOrganizationOwnedDevice(caller)
-                    || isDeviceOwner(caller));
+                    || isDefaultDeviceOwner(caller));
         } else {
             // A delegate app passes a null admin component, which is expected
             Preconditions.checkCallAuthorization(
@@ -15007,7 +15114,7 @@
         if (admin != null) {
             Preconditions.checkCallAuthorization(
                     isProfileOwnerOfOrganizationOwnedDevice(caller)
-                    || isDeviceOwner(caller));
+                    || isDefaultDeviceOwner(caller));
         } else {
             // A delegate app passes a null admin component, which is expected
             Preconditions.checkCallAuthorization(
@@ -15246,7 +15353,8 @@
         Objects.requireNonNull(admin, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+                || isProfileOwner(caller) || isFinancedDeviceOwner(caller));
 
         toggleBackupServiceActive(caller.getUserId(), enabled);
     }
@@ -15259,7 +15367,8 @@
         Objects.requireNonNull(admin, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+                || isProfileOwner(caller) || isFinancedDeviceOwner(caller));
 
         return mInjector.binderWithCleanCallingIdentity(() -> {
             synchronized (getLockObject()) {
@@ -15334,7 +15443,8 @@
         }
         Objects.requireNonNull(admin);
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
 
         synchronized (getLockObject()) {
             final int callingUserId = caller.getUserId();
@@ -15462,7 +15572,7 @@
         final boolean isManagedProfileOwner = isProfileOwner(caller)
                 && isManagedProfile(caller.getUserId());
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                && (isDeviceOwner(caller) || isManagedProfileOwner))
+                && (isDefaultDeviceOwner(caller) || isManagedProfileOwner))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING)));
 
         synchronized (getLockObject()) {
@@ -15622,7 +15732,7 @@
         }
         final CallerIdentity caller = getCallerIdentity(admin, packageName);
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                &&  (isDeviceOwner(caller)
+                &&  (isDefaultDeviceOwner(caller)
                 || (isProfileOwner(caller) && isManagedProfile(caller.getUserId()))))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING))
                 || hasCallingOrSelfPermission(permission.MANAGE_USERS));
@@ -15654,7 +15764,7 @@
         final boolean isManagedProfileOwner = isProfileOwner(caller)
                 && isManagedProfile(caller.getUserId());
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                &&  (isDeviceOwner(caller) || isManagedProfileOwner))
+                &&  (isDefaultDeviceOwner(caller) || isManagedProfileOwner))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING)));
         if (mOwners.hasDeviceOwner()) {
             checkAllUsersAreAffiliatedWithDevice();
@@ -15815,14 +15925,16 @@
     @Override
     public long getLastSecurityLogRetrievalTime() {
         final CallerIdentity caller = getCallerIdentity();
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || canManageUsers(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || canManageUsers(caller));
         return getUserData(UserHandle.USER_SYSTEM).mLastSecurityLogRetrievalTime;
      }
 
     @Override
     public long getLastBugReportRequestTime() {
         final CallerIdentity caller = getCallerIdentity();
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || canManageUsers(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || canManageUsers(caller));
         return getUserData(UserHandle.USER_SYSTEM).mLastBugReportRequestTime;
      }
 
@@ -15830,7 +15942,7 @@
     public long getLastNetworkLogRetrievalTime() {
         final CallerIdentity caller = getCallerIdentity();
 
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller)
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
                 || (isProfileOwner(caller) && isManagedProfile(caller.getUserId()))
                 || canManageUsers(caller));
         final int affectedUserId = getNetworkLoggingAffectedUser();
@@ -15846,7 +15958,8 @@
             throw new IllegalArgumentException("token must be at least 32-byte long");
         }
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
 
         synchronized (getLockObject()) {
             final int userHandle = caller.getUserId();
@@ -15870,7 +15983,8 @@
             return false;
         }
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
 
         synchronized (getLockObject()) {
             final int userHandle = caller.getUserId();
@@ -15895,7 +16009,8 @@
             return false;
         }
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
 
         synchronized (getLockObject()) {
             return isResetPasswordTokenActiveForUserLocked(caller.getUserId());
@@ -15920,7 +16035,8 @@
         Objects.requireNonNull(token);
 
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
 
         synchronized (getLockObject()) {
             DevicePolicyData policy = getUserData(caller.getUserId());
@@ -15945,7 +16061,7 @@
     @Override
     public boolean isCurrentInputMethodSetByOwner() {
         final CallerIdentity caller = getCallerIdentity();
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller)
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
                 || isProfileOwner(caller) || isSystemUid(caller),
                 "Only profile owner, device owner and system may call this method.");
         return getUserData(caller.getUserId()).mCurrentInputMethodSet;
@@ -15956,7 +16072,7 @@
         final int userId = user.getIdentifier();
         final CallerIdentity caller = getCallerIdentity();
         Preconditions.checkCallAuthorization((userId == caller.getUserId())
-                || isProfileOwner(caller) || isDeviceOwner(caller)
+                || isProfileOwner(caller) || isDefaultDeviceOwner(caller)
                 || hasFullCrossUsersPermission(caller, userId));
 
         synchronized (getLockObject()) {
@@ -15973,7 +16089,8 @@
         Objects.requireNonNull(callback, "callback is null");
 
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_CLEAR_APPLICATION_USER_DATA);
 
         long ident = mInjector.binderClearCallingIdentity();
@@ -16005,7 +16122,7 @@
         }
         Objects.requireNonNull(admin, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_LOGOUT_ENABLED);
 
         synchronized (getLockObject()) {
@@ -16054,7 +16171,8 @@
                 "Provided administrator and target have the same package name.");
 
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
 
         final int callingUserId = caller.getUserId();
         final DevicePolicyData policy = getUserData(callingUserId);
@@ -16096,7 +16214,7 @@
                     if (isUserAffiliatedWithDeviceLocked(callingUserId)) {
                         notifyAffiliatedProfileTransferOwnershipComplete(callingUserId);
                     }
-                } else if (isDeviceOwner(caller)) {
+                } else if (isDefaultDeviceOwner(caller)) {
                     ownerType = ADMIN_TYPE_DEVICE_OWNER;
                     prepareTransfer(admin, target, bundle, callingUserId,
                             ADMIN_TYPE_DEVICE_OWNER);
@@ -16177,7 +16295,7 @@
         }
         Objects.requireNonNull(admin, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
 
         final String startUserSessionMessageString =
                 startUserSessionMessage != null ? startUserSessionMessage.toString() : null;
@@ -16202,7 +16320,7 @@
         }
         Objects.requireNonNull(admin, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
 
         final String endUserSessionMessageString =
                 endUserSessionMessage != null ? endUserSessionMessage.toString() : null;
@@ -16227,7 +16345,7 @@
         }
         Objects.requireNonNull(admin, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
 
         synchronized (getLockObject()) {
             final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
@@ -16242,7 +16360,7 @@
         }
         Objects.requireNonNull(admin, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
 
         synchronized (getLockObject()) {
             final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
@@ -16258,7 +16376,8 @@
     @Nullable
     public PersistableBundle getTransferOwnershipBundle() {
         final CallerIdentity caller = getCallerIdentity();
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
 
         synchronized (getLockObject()) {
             final int callingUserId = caller.getUserId();
@@ -16288,7 +16407,7 @@
         Objects.requireNonNull(who, "ComponentName is null");
         Objects.requireNonNull(apnSetting, "ApnSetting is null in addOverrideApn");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
 
         TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
         if (tm != null) {
@@ -16309,7 +16428,7 @@
         Objects.requireNonNull(who, "ComponentName is null");
         Objects.requireNonNull(apnSetting, "ApnSetting is null in updateOverrideApn");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
 
         if (apnId < 0) {
             return false;
@@ -16331,7 +16450,7 @@
         }
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
         return removeOverrideApnUnchecked(apnId);
     }
 
@@ -16352,7 +16471,7 @@
         }
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
         return getOverrideApnsUnchecked();
     }
 
@@ -16373,7 +16492,7 @@
         }
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_OVERRIDE_APNS_ENABLED);
 
         setOverrideApnsEnabledUnchecked(enabled);
@@ -16393,7 +16512,7 @@
         }
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
 
         Cursor enforceCursor = mInjector.binderWithCleanCallingIdentity(
                 () -> mContext.getContentResolver().query(
@@ -16476,7 +16595,7 @@
         }
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
         checkAllUsersAreAffiliatedWithDevice();
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_GLOBAL_PRIVATE_DNS);
 
@@ -16515,7 +16634,7 @@
         }
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
 
         final int currentMode = ConnectivitySettingsManager.getPrivateDnsMode(mContext);
         switch (currentMode) {
@@ -16537,7 +16656,7 @@
         }
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
         return mInjector.settingsGlobalGetString(PRIVATE_DNS_SPECIFIER);
     }
 
@@ -16547,8 +16666,8 @@
         Objects.requireNonNull(admin, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller)
-                || isProfileOwnerOfOrganizationOwnedDevice(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_INSTALL_SYSTEM_UPDATE);
 
         DevicePolicyEventLogger
@@ -16923,7 +17042,8 @@
         Objects.requireNonNull(who, "ComponentName is null");
         Objects.requireNonNull(packages, "packages is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller));
         checkCanExecuteOrThrowUnsafe(
                 DevicePolicyManager.OPERATION_SET_USER_CONTROL_DISABLED_PACKAGES);
 
@@ -16942,7 +17062,8 @@
         Objects.requireNonNull(who, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller));
 
         synchronized (getLockObject()) {
             return mOwners.getDeviceOwnerProtectedPackages(who.getPackageName());
@@ -16954,7 +17075,7 @@
         Objects.requireNonNull(who, "Admin component name must be provided");
         final CallerIdentity caller = getCallerIdentity(who);
         Preconditions.checkCallAuthorization(
-                isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+                isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
                 "Common Criteria mode can only be controlled by a device owner or "
                         + "a profile owner on an organization-owned device.");
         synchronized (getLockObject()) {
@@ -16974,7 +17095,7 @@
         if (who != null) {
             final CallerIdentity caller = getCallerIdentity(who);
             Preconditions.checkCallAuthorization(
-                    isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+                    isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
                     "Common Criteria mode can only be controlled by a device owner or "
                             + "a profile owner on an organization-owned device.");
 
@@ -17446,7 +17567,7 @@
 
         final CallerIdentity caller = getCallerIdentity(callerPackage);
         Preconditions.checkCallAuthorization(
-                isDeviceOwner(caller) || isProfileOwner(caller)
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller)
                         || isCallerDelegate(caller, DELEGATION_CERT_INSTALL));
 
         synchronized (getLockObject()) {
@@ -17467,7 +17588,7 @@
 
         final CallerIdentity caller = getCallerIdentity(callerPackage);
         // Only the DPC can set this ID.
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller),
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) || isProfileOwner(caller),
                 "Only a Device Owner or Profile Owner may set the Enterprise ID.");
         // Empty enterprise ID must not be provided in calls to this method.
         Preconditions.checkArgument(!TextUtils.isEmpty(organizationId),
@@ -18141,27 +18262,51 @@
             @DeviceOwnerType int deviceOwnerType) {
         Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
                 permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
-        verifyDeviceOwnerTypePreconditions(admin);
 
-        final String packageName = admin.getPackageName();
+        synchronized (getLockObject()) {
+            setDeviceOwnerTypeLocked(admin, deviceOwnerType);
+        }
+    }
+
+    private void setDeviceOwnerTypeLocked(ComponentName admin,
+            @DeviceOwnerType int deviceOwnerType) {
+        String packageName = admin.getPackageName();
+
+        verifyDeviceOwnerTypePreconditionsLocked(admin);
         Preconditions.checkState(!mOwners.isDeviceOwnerTypeSetForDeviceOwner(packageName),
                 "The device owner type has already been set for " + packageName);
 
-        synchronized (getLockObject()) {
-            mOwners.setDeviceOwnerType(packageName, deviceOwnerType);
-        }
+        mOwners.setDeviceOwnerType(packageName, deviceOwnerType);
     }
 
     @Override
     @DeviceOwnerType
     public int getDeviceOwnerType(@NonNull ComponentName admin) {
-        verifyDeviceOwnerTypePreconditions(admin);
         synchronized (getLockObject()) {
-            return mOwners.getDeviceOwnerType(admin.getPackageName());
+            verifyDeviceOwnerTypePreconditionsLocked(admin);
+            return getDeviceOwnerTypeLocked(admin.getPackageName());
         }
     }
 
-    private void verifyDeviceOwnerTypePreconditions(@NonNull ComponentName admin) {
+    @DeviceOwnerType
+    private int getDeviceOwnerTypeLocked(String packageName) {
+        return mOwners.getDeviceOwnerType(packageName);
+    }
+
+    /**
+     * {@code true} is returned <b>only if</b> the caller is the device owner and the device owner
+     * type is {@link DevicePolicyManager#DEVICE_OWNER_TYPE_FINANCED}. {@code false} is returned for
+     * the case where the caller is not the device owner, there is no device owner, or the device
+     * owner type is not {@link DevicePolicyManager#DEVICE_OWNER_TYPE_FINANCED}.
+     */
+    private boolean isFinancedDeviceOwner(CallerIdentity caller) {
+        synchronized (getLockObject()) {
+            return isDeviceOwnerLocked(caller) && getDeviceOwnerTypeLocked(
+                    mOwners.getDeviceOwnerPackageName()) == DEVICE_OWNER_TYPE_FINANCED;
+        }
+    }
+
+    private void verifyDeviceOwnerTypePreconditionsLocked(@NonNull ComponentName admin) {
         Preconditions.checkState(mOwners.hasDeviceOwner(), "there is no device owner");
         Preconditions.checkState(mOwners.getDeviceOwnerComponent().equals(admin),
                 "admin is not the device owner");
@@ -18172,7 +18317,7 @@
         Objects.requireNonNull(packageName, "Admin package name must be provided");
         final CallerIdentity caller = getCallerIdentity(packageName);
         Preconditions.checkCallAuthorization(
-                isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+                isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
                 "USB data signaling can only be controlled by a device owner or "
                         + "a profile owner on an organization-owned device.");
         Preconditions.checkState(canUsbDataSignalingBeDisabled(),
@@ -18213,7 +18358,7 @@
         synchronized (getLockObject()) {
             // If the caller is an admin, return the policy set by itself. Otherwise
             // return the device-wide policy.
-            if (isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)) {
+            if (isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)) {
                 return getProfileOwnerOrDeviceOwnerLocked(caller).mUsbDataSignalingEnabled;
             } else {
                 return isUsbDataSignalingEnabledInternalLocked();
@@ -18254,7 +18399,7 @@
     public void setMinimumRequiredWifiSecurityLevel(int level) {
         final CallerIdentity caller = getCallerIdentity();
         Preconditions.checkCallAuthorization(
-                isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+                isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
                 "Wi-Fi minimum security level can only be controlled by a device owner or "
                         + "a profile owner on an organization-owned device.");
 
@@ -18284,7 +18429,7 @@
     public void setSsidAllowlist(List<String> ssids) {
         final CallerIdentity caller = getCallerIdentity();
         Preconditions.checkCallAuthorization(
-                isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+                isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
                 "SSID allowlist can only be controlled by a device owner or "
                         + "a profile owner on an organization-owned device.");
 
@@ -18306,7 +18451,7 @@
     public List<String> getSsidAllowlist() {
         final CallerIdentity caller = getCallerIdentity();
         Preconditions.checkCallAuthorization(
-                isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)
+                isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)
                         || isSystemUid(caller),
                 "SSID allowlist can only be retrieved by a device owner or "
                         + "a profile owner on an organization-owned device or a system app.");
@@ -18322,7 +18467,7 @@
     public void setSsidDenylist(List<String> ssids) {
         final CallerIdentity caller = getCallerIdentity();
         Preconditions.checkCallAuthorization(
-                isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+                isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
                 "SSID denylist can only be controlled by a device owner or "
                         + "a profile owner on an organization-owned device.");
 
@@ -18344,7 +18489,7 @@
     public List<String> getSsidDenylist() {
         final CallerIdentity caller = getCallerIdentity();
         Preconditions.checkCallAuthorization(
-                isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)
+                isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)
                         || isSystemUid(caller),
                 "SSID denylist can only be retrieved by a device owner or "
                         + "a profile owner on an organization-owned device or a system app.");
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java b/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
index 685cf05..598f9e8 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
@@ -34,6 +34,7 @@
 import android.annotation.UserIdInt;
 import android.app.admin.DeviceAdminReceiver;
 import android.app.admin.DevicePolicyManager;
+import android.app.role.RoleManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -41,6 +42,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.os.Binder;
 import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
 import android.view.inputmethod.InputMethodInfo;
@@ -91,6 +93,8 @@
         List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId);
 
         String getActiveApexPackageNameContainingPackage(String packageName);
+
+        String getDeviceManagerRoleHolderPackageName(Context context);
     }
 
     private static final class DefaultInjector implements Injector {
@@ -104,6 +108,19 @@
         public String getActiveApexPackageNameContainingPackage(String packageName) {
             return ApexManager.getInstance().getActiveApexPackageNameContainingPackage(packageName);
         }
+
+        @Override
+        public String getDeviceManagerRoleHolderPackageName(Context context) {
+            return Binder.withCleanCallingIdentity(() -> {
+                RoleManager roleManager = context.getSystemService(RoleManager.class);
+                List<String> roleHolders =
+                        roleManager.getRoleHolders(RoleManager.ROLE_DEVICE_MANAGER);
+                if (roleHolders.isEmpty()) {
+                    return null;
+                }
+                return roleHolders.get(0);
+            });
+        }
     }
 
     @VisibleForTesting
@@ -142,9 +159,20 @@
         nonRequiredApps.addAll(getDisallowedApps(provisioningAction));
         nonRequiredApps.removeAll(
                 getRequiredAppsMainlineModules(nonRequiredApps, provisioningAction));
+        nonRequiredApps.removeAll(getDeviceManagerRoleHolders());
         return nonRequiredApps;
     }
 
+    private Set<String> getDeviceManagerRoleHolders() {
+        HashSet<String> result = new HashSet<>();
+        String deviceManagerRoleHolderPackageName =
+                mInjector.getDeviceManagerRoleHolderPackageName(mContext);
+        if (deviceManagerRoleHolderPackageName != null) {
+            result.add(deviceManagerRoleHolderPackageName);
+        }
+        return result;
+    }
+
     /**
      * Returns a subset of {@code packageNames} whose packages are mainline modules declared as
      * required apps via their app metadata.
diff --git a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java
index e40f543..e1aa08d 100644
--- a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java
@@ -23,6 +23,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.AdditionalAnswers.returnsArgAt;
 import static org.mockito.ArgumentMatchers.any;
@@ -40,6 +41,7 @@
 import android.app.usage.UsageEvents.Event;
 import android.app.usage.UsageStatsManagerInternal;
 import android.app.usage.UsageStatsManagerInternal.UsageEventListener;
+import android.apphibernation.HibernationStats;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -68,6 +70,8 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.Executor;
 
 /**
@@ -126,7 +130,7 @@
         mUsageEventListener = mUsageEventListenerCaptor.getValue();
 
         doReturn(mUserInfos).when(mUserManager).getUsers();
-
+        doReturn(true).when(mPackageManagerInternal).canQueryPackage(anyInt(), any());
         doAnswer(returnsArgAt(2)).when(mIActivityManager).handleIncomingUser(anyInt(), anyInt(),
                 anyInt(), anyBoolean(), anyBoolean(), any(), any());
 
@@ -376,6 +380,58 @@
         assertFalse(mAppHibernationService.isHibernatingGlobally(PACKAGE_NAME_1));
     }
 
+    @Test
+    public void testGetHibernationStatsForUser_getsStatsForPackage() {
+        // GIVEN a package is hibernating globally and for a user
+        mAppHibernationService.setHibernatingGlobally(PACKAGE_NAME_1, true);
+        mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_1, true);
+
+        // WHEN we ask for the hibernation stats for the package
+        Map<String, HibernationStats> stats =
+                mAppHibernationService.getHibernationStatsForUser(
+                        Set.of(PACKAGE_NAME_1), USER_ID_1);
+
+        // THEN the stats exist for the package
+        assertTrue(stats.containsKey(PACKAGE_NAME_1));
+    }
+
+    @Test
+    public void testGetHibernationStatsForUser_noExceptionThrownWhenPackageDoesntExist() {
+        // WHEN we ask for the hibernation stats for a package that doesn't exist
+        Map<String, HibernationStats> stats =
+                mAppHibernationService.getHibernationStatsForUser(
+                        Set.of(PACKAGE_NAME_1), USER_ID_1);
+
+        // THEN no exception is thrown and empty stats are returned
+        assertNotNull(stats);
+    }
+
+    @Test
+    public void testGetHibernationStatsForUser_returnsAllIfNoPackagesSpecified()
+            throws RemoteException {
+        // GIVEN an unlocked user with all packages installed and they're all hibernating
+        UserInfo userInfo =
+                addUser(USER_ID_2, new String[]{PACKAGE_NAME_1, PACKAGE_NAME_2, PACKAGE_NAME_3});
+        doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(USER_ID_2);
+        mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(userInfo));
+        mAppHibernationService.setHibernatingGlobally(PACKAGE_NAME_1, true);
+        mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_2, true);
+        mAppHibernationService.setHibernatingGlobally(PACKAGE_NAME_2, true);
+        mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_2, USER_ID_2, true);
+        mAppHibernationService.setHibernatingGlobally(PACKAGE_NAME_3, true);
+        mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_3, USER_ID_2, true);
+
+        // WHEN we ask for the hibernation stats with no package specified
+        Map<String, HibernationStats> stats =
+                mAppHibernationService.getHibernationStatsForUser(
+                        null /* packageNames */, USER_ID_2);
+
+        // THEN all the package stats are returned
+        assertTrue(stats.containsKey(PACKAGE_NAME_1));
+        assertTrue(stats.containsKey(PACKAGE_NAME_2));
+        assertTrue(stats.containsKey(PACKAGE_NAME_3));
+    }
+
     /**
      * Mock a usage event occurring.
      *
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
new file mode 100644
index 0000000..ec884f5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
@@ -0,0 +1,115 @@
+/*
+ * 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.biometrics.log;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+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 android.hardware.biometrics.IBiometricContextListener;
+import android.hardware.biometrics.common.OperationContext;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.statusbar.IStatusBarService;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+@Presubmit
+@SmallTest
+public class BiometricContextProviderTest {
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private IStatusBarService mStatusBarService;
+
+    private OperationContext mOpContext = new OperationContext();
+    private IBiometricContextListener mListener;
+    private BiometricContextProvider mProvider;
+
+    @Before
+    public void setup() throws RemoteException {
+        mProvider = new BiometricContextProvider(mStatusBarService, null /* handler */);
+        ArgumentCaptor<IBiometricContextListener> captor =
+                ArgumentCaptor.forClass(IBiometricContextListener.class);
+        verify(mStatusBarService).setBiometicContextListener(captor.capture());
+        mListener = captor.getValue();
+    }
+
+    @Test
+    public void testIsAoD() throws RemoteException {
+        mListener.onDozeChanged(true);
+        assertThat(mProvider.isAoD()).isTrue();
+        mListener.onDozeChanged(false);
+        assertThat(mProvider.isAoD()).isFalse();
+    }
+
+    @Test
+    public void testSubscribesToAoD() throws RemoteException {
+        final List<Boolean> expected = ImmutableList.of(true, false, true, true, false);
+        final List<Boolean> actual = new ArrayList<>();
+
+        mProvider.subscribe(mOpContext, ctx -> {
+            assertThat(ctx).isSameInstanceAs(mOpContext);
+            actual.add(ctx.isAoD);
+        });
+
+        for (boolean v : expected) {
+            mListener.onDozeChanged(v);
+        }
+
+        assertThat(actual).containsExactlyElementsIn(expected).inOrder();
+    }
+
+    @Test
+    public void testUnsubscribes() throws RemoteException {
+        final Consumer<OperationContext> emptyConsumer = mock(Consumer.class);
+        mProvider.subscribe(mOpContext, emptyConsumer);
+        mProvider.unsubscribe(mOpContext);
+
+        mListener.onDozeChanged(true);
+
+        final Consumer<OperationContext> nonEmptyConsumer = mock(Consumer.class);
+        mProvider.subscribe(mOpContext, nonEmptyConsumer);
+        mListener.onDozeChanged(false);
+        mProvider.unsubscribe(mOpContext);
+        mListener.onDozeChanged(true);
+
+        verify(emptyConsumer, never()).accept(any());
+        verify(nonEmptyConsumer).accept(same(mOpContext));
+    }
+}
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 2b72fab..b0eb810 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
@@ -44,7 +44,8 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 
 @Presubmit
 @SmallTest
@@ -55,6 +56,9 @@
     private static final int DEFAULT_CLIENT = BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT;
 
     @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Rule
     public TestableContext mContext = new TestableContext(
             InstrumentationRegistry.getInstrumentation().getContext());
     @Mock
@@ -68,8 +72,6 @@
 
     @Before
     public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
         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,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java
index 9bb722f..2d9d868 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java
@@ -22,6 +22,7 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -34,6 +35,9 @@
 import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
@@ -92,9 +96,8 @@
                 @NonNull Supplier<Object> lazyDaemon, @NonNull IBinder token,
                 @NonNull ClientMonitorCallbackConverter callback) {
             super(context, lazyDaemon, token, callback, 0 /* userId */, "Test", 0 /* cookie */,
-                    TEST_SENSOR_ID /* sensorId */, true /* shouldVibrate */, 0 /* statsModality */,
-                    0 /* statsAction */,
-                    0 /* statsClient */);
+                    TEST_SENSOR_ID /* sensorId */, true /* shouldVibrate */,
+                    mock(BiometricLogger.class), mock(BiometricContext.class));
         }
 
         @Override
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java
index 51d234d..8e6d90c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java
@@ -30,6 +30,7 @@
 import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
 
+import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 
 import org.junit.Before;
@@ -49,6 +50,8 @@
     @Mock
     private BiometricLogger mLogger;
     @Mock
+    private BiometricContext mBiometricContext;
+    @Mock
     private ClientMonitorCallback mCallback;
 
     private TestClientMonitor mClientMonitor;
@@ -109,7 +112,7 @@
 
         TestClientMonitor() {
             super(mContext, mToken, mListener, 9 /* userId */, "foo" /* owner */, 2 /* cookie */,
-                    5 /* sensorId */, mLogger);
+                    5 /* sensorId */, mLogger, mBiometricContext);
         }
 
         @Override
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
index 8751cf3..64be569 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
@@ -36,6 +36,9 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -54,7 +57,8 @@
     public abstract static class InterruptableMonitor<T>
             extends HalClientMonitor<T> implements  Interruptable {
         public InterruptableMonitor() {
-            super(null, null, null, null, 0, null, 0, 0, 0, 0, 0);
+            super(null, null, null, null, 0, null, 0, 0,
+                    mock(BiometricLogger.class), mock(BiometricContext.class));
         }
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index ecd9abc..0fa2b41 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -51,6 +51,8 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.nano.BiometricSchedulerProto;
 import com.android.server.biometrics.nano.BiometricsProto;
 
@@ -526,10 +528,10 @@
                 @NonNull ClientMonitorCallbackConverter listener) {
             super(context, lazyDaemon, token, listener, 0 /* targetUserId */, 0 /* operationId */,
                     false /* restricted */, TAG, 1 /* cookie */, false /* requireConfirmation */,
-                    TEST_SENSOR_ID, true /* isStrongBiometric */, 0 /* statsModality */,
-                    0 /* statsClient */, null /* taskStackListener */, mock(LockoutTracker.class),
-                    false /* isKeyguard */, true /* shouldVibrate */,
-                    false /* isKeyguardBypassEnabled */);
+                    TEST_SENSOR_ID, mock(BiometricLogger.class), mock(BiometricContext.class),
+                    true /* isStrongBiometric */, null /* taskStackListener */,
+                    mock(LockoutTracker.class), false /* isKeyguard */,
+                    true /* shouldVibrate */, false /* isKeyguardBypassEnabled */);
         }
 
         @Override
@@ -573,8 +575,9 @@
                 @NonNull ClientMonitorCallbackConverter listener) {
             super(context, lazyDaemon, token, listener, 0 /* userId */, new byte[69],
                     "test" /* owner */, mock(BiometricUtils.class),
-                    5 /* timeoutSec */, 0 /* statsModality */, TEST_SENSOR_ID,
-                    true /* shouldVibrate */);
+                    5 /* timeoutSec */, TEST_SENSOR_ID,
+                    true /* shouldVibrate */,
+                    mock(BiometricLogger.class), mock(BiometricContext.class));
         }
 
         @Override
@@ -613,8 +616,8 @@
         TestHalClientMonitor(@NonNull Context context, @NonNull IBinder token,
                 @NonNull Supplier<Object> lazyDaemon, int cookie, int protoEnum) {
             super(context, lazyDaemon, token /* token */, null /* listener */, 0 /* userId */,
-                    TAG, cookie, TEST_SENSOR_ID, 0 /* statsModality */,
-                    0 /* statsAction */, 0 /* statsClient */);
+                    TAG, cookie, TEST_SENSOR_ID,
+                    mock(BiometricLogger.class), mock(BiometricContext.class));
             mProtoEnum = protoEnum;
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java
index 587bb60..092ca19 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java
@@ -64,7 +64,7 @@
         ClientMonitorCompositeCallback callback = new ClientMonitorCompositeCallback(callbacks);
 
         callback.onClientStarted(mClientMonitor);
-        final InOrder order = inOrder(expected);
+        final InOrder order = inOrder((Object[]) expected);
         for (ClientMonitorCallback cb : expected) {
             order.verify(cb).onClientStarted(eq(mClientMonitor));
         }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
index 30777cd..52eee9a 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
@@ -41,6 +41,9 @@
 import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -66,6 +69,10 @@
     private Context mContext;
     @Mock
     private IBiometricService mBiometricService;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
 
     private TestUserStartedCallback mUserStartedCallback = new TestUserStartedCallback();
     private TestUserStoppedCallback mUserStoppedCallback = new TestUserStoppedCallback();
@@ -88,7 +95,8 @@
                     @Override
                     public StopUserClient<?> getStopUserClient(int userId) {
                         return new TestStopUserClient(mContext, Object::new, mToken, userId,
-                                TEST_SENSOR_ID, mUserStoppedCallback);
+                                TEST_SENSOR_ID, mBiometricLogger, mBiometricContext,
+                                mUserStoppedCallback);
                     }
 
                     @NonNull
@@ -96,7 +104,8 @@
                     public StartUserClient<?, ?> getStartUserClient(int newUserId) {
                         mStartUserClientCount++;
                         return new TestStartUserClient(mContext, Object::new, mToken, newUserId,
-                                TEST_SENSOR_ID, mUserStartedCallback, mStartOperationsFinish);
+                                TEST_SENSOR_ID,  mBiometricLogger, mBiometricContext,
+                                mUserStartedCallback, mStartOperationsFinish);
                     }
                 },
                 CoexCoordinator.getInstance());
@@ -226,8 +235,10 @@
     private static class TestStopUserClient extends StopUserClient<Object> {
         public TestStopUserClient(@NonNull Context context,
                 @NonNull Supplier<Object> lazyDaemon, @Nullable IBinder token, int userId,
-                int sensorId, @NonNull UserStoppedCallback callback) {
-            super(context, lazyDaemon, token, userId, sensorId, callback);
+                int sensorId, @NonNull BiometricLogger logger,
+                @NonNull BiometricContext biometricContext,
+                @NonNull UserStoppedCallback callback) {
+            super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback);
         }
 
         @Override
@@ -254,8 +265,10 @@
 
         public TestStartUserClient(@NonNull Context context,
                 @NonNull Supplier<Object> lazyDaemon, @Nullable IBinder token, int userId,
-                int sensorId, @NonNull UserStartedCallback<Object> callback, boolean shouldFinish) {
-            super(context, lazyDaemon, token, userId, sensorId, callback);
+                int sensorId, @NonNull BiometricLogger logger,
+                @NonNull BiometricContext biometricContext,
+                @NonNull UserStartedCallback<Object> callback, boolean shouldFinish) {
+            super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback);
             mShouldFinish = shouldFinish;
         }
 
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
new file mode 100644
index 0000000..25585dd
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
@@ -0,0 +1,128 @@
+/*
+ * 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.biometrics.sensors.face.aidl;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+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.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.testing.TestableContext;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.LockoutCache;
+import com.android.server.biometrics.sensors.face.UsageStats;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FaceAuthenticationClientTest {
+
+    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(
+            InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+
+    @Mock
+    private ISession mHal;
+    @Mock
+    private IBinder mToken;
+    @Mock
+    private ClientMonitorCallbackConverter mClientMonitorCallbackConverter;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+    @Mock
+    private LockoutCache mLockoutCache;
+    @Mock
+    private UsageStats mUsageStats;
+    @Mock
+    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.isAoD()).thenReturn(HAS_AOD);
+    }
+
+    @Test
+    public void authNoContext_v1() throws RemoteException {
+        final FaceAuthenticationClient client = createClient(1);
+        client.start(mCallback);
+
+        verify(mHal).authenticate(eq(OP_ID));
+        verify(mHal, never()).authenticateWithContext(anyLong(), any());
+    }
+
+    @Test
+    public void authWithContext_v2() throws RemoteException {
+        final FaceAuthenticationClient client = createClient(2);
+        client.start(mCallback);
+
+        verify(mHal).authenticateWithContext(eq(OP_ID), mOperationContextCaptor.capture());
+        verify(mHal, never()).authenticate(anyLong());
+
+        final OperationContext opContext = mOperationContextCaptor.getValue();
+        assertThat(opContext.isAoD).isEqualTo(HAS_AOD);
+    }
+
+    private FaceAuthenticationClient createClient(int version) throws RemoteException {
+        when(mHal.getInterfaceVersion()).thenReturn(version);
+
+        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+        return new FaceAuthenticationClient(mContext, () -> aidl, mToken,
+                2 /* requestId */, mClientMonitorCallbackConverter, 5 /* targetUserId */, OP_ID,
+                false /* restricted */, "test-owner", 4 /* cookie */,
+                false /* requireConfirmation */, 9 /* sensorId */,
+                mBiometricLogger, mBiometricContext, true /* isStrongBiometric */,
+                mUsageStats, mLockoutCache, false /* allowBackgroundAuthentication */,
+                false /* isKeyguardBypassEnabled */, null /* sensorPrivacyManager */);
+    }
+}
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
new file mode 100644
index 0000000..6c72ebf
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.biometrics.sensors.face.aidl;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.face.ISession;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.testing.TestableContext;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+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.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FaceDetectClientTest {
+
+    private static final int USER_ID = 12;
+    private static final boolean HAS_AOD = true;
+
+    @Rule
+    public final TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+
+    @Mock
+    private ISession mHal;
+    @Mock
+    private IBinder mToken;
+    @Mock
+    private ClientMonitorCallbackConverter mClientMonitorCallbackConverter;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+    @Mock
+    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.isAoD()).thenReturn(HAS_AOD);
+    }
+
+    @Test
+    public void detectNoContext_v1() throws RemoteException {
+        final FaceDetectClient client = createClient(1);
+        client.start(mCallback);
+
+        verify(mHal).detectInteraction();
+        verify(mHal, never()).detectInteractionWithContext(any());
+    }
+
+    @Test
+    public void detectWithContext_v2() throws RemoteException {
+        final FaceDetectClient client = createClient(2);
+        client.start(mCallback);
+
+        verify(mHal).detectInteractionWithContext(mOperationContextCaptor.capture());
+        verify(mHal, never()).detectInteraction();
+
+        final OperationContext opContext = mOperationContextCaptor.getValue();
+        assertThat(opContext.isAoD).isEqualTo(HAS_AOD);
+    }
+
+    private FaceDetectClient createClient(int version) throws RemoteException {
+        when(mHal.getInterfaceVersion()).thenReturn(version);
+
+        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+        return new FaceDetectClient(mContext, () -> aidl, mToken,
+                99 /* requestId */, mClientMonitorCallbackConverter, USER_ID,
+                "own-it", 5 /* sensorId */, mBiometricLogger, mBiometricContext,
+                false /* isStrongBiometric */, null /* sensorPrivacyManager */);
+    }
+}
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
new file mode 100644
index 0000000..22070e9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.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.server.biometrics.sensors.face.aidl;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyByte;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.face.ISession;
+import android.hardware.face.Face;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.testing.TestableContext;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FaceEnrollClientTest {
+
+    private static final byte[] HAT = new byte[69];
+    private static final int USER_ID = 12;
+
+    @Rule
+    public final TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+
+    @Mock
+    private ISession mHal;
+    @Mock
+    private IBinder mToken;
+    @Mock
+    private ClientMonitorCallbackConverter mClientMonitorCallbackConverter;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+    @Mock
+    private BiometricUtils<Face> mUtils;
+    @Mock
+    private ClientMonitorCallback mCallback;
+    @Mock
+    private Sensor.HalSessionCallback mHalSessionCallback;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Test
+    public void enrollNoContext_v1() throws RemoteException {
+        final FaceEnrollClient client = createClient(1);
+        client.start(mCallback);
+
+        verify(mHal).enroll(any(), anyByte(), any(), any());
+        verify(mHal, never()).enrollWithContext(any(), anyByte(), any(), any(), any());
+    }
+
+    @Test
+    public void enrollWithContext_v2() throws RemoteException {
+        final FaceEnrollClient client = createClient(2);
+        client.start(mCallback);
+
+        verify(mHal).enrollWithContext(any(), anyByte(), any(), any(), any());
+        verify(mHal, never()).enroll(any(), anyByte(), any(), any());
+    }
+
+    private FaceEnrollClient createClient(int version) throws RemoteException {
+        when(mHal.getInterfaceVersion()).thenReturn(version);
+
+        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+        return new FaceEnrollClient(mContext, () -> aidl, mToken, mClientMonitorCallbackConverter,
+                USER_ID, HAT, "com.foo.bar", 44 /* requestId */,
+                mUtils, new int[0] /* disabledFeatures */, 6 /* timeoutSec */,
+                null /* previewSurface */, 8 /* sensorId */,
+                mBiometricLogger, mBiometricContext, 5 /* maxTemplatesPerUser */,
+                true /* debugConsent */);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
index 61e4776..b60324e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
@@ -32,6 +32,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.CoexCoordinator;
 import com.android.server.biometrics.sensors.LockoutCache;
@@ -66,6 +68,10 @@
     private Sensor.HalSessionCallback.Callback mHalSessionCallback;
     @Mock
     private LockoutResetDispatcher mLockoutResetDispatcher;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
 
     private final TestLooper mLooper = new TestLooper();
     private final LockoutCache mLockoutCache = new LockoutCache();
@@ -102,7 +108,8 @@
 
         mScheduler.scheduleClientMonitor(new FaceResetLockoutClient(mContext,
                 () -> new AidlSession(1, mSession, USER_ID, mHalCallback),
-                USER_ID, TAG, SENSOR_ID, HAT, mLockoutCache, mLockoutResetDispatcher));
+                USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext,
+                HAT, mLockoutCache, mLockoutResetDispatcher));
         mLooper.dispatchAll();
 
         verifyNotLocked();
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java
index 931fad1..ec08329 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java
@@ -34,6 +34,8 @@
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.SmallTest;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 
@@ -62,6 +64,10 @@
     private IFaceServiceReceiver mOtherReceiver;
     @Mock
     private ClientMonitorCallback mMonitorCallback;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
 
     private FaceGenerateChallengeClient mClient;
 
@@ -75,7 +81,7 @@
 
         mClient = new FaceGenerateChallengeClient(mContext, () -> mIBiometricsFace, new Binder(),
                 new ClientMonitorCallbackConverter(mClientReceiver), USER_ID,
-                TAG, SENSOR_ID, START_TIME);
+                TAG, SENSOR_ID, mBiometricLogger, mBiometricContext , START_TIME);
     }
 
     @Test
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
new file mode 100644
index 0000000..5c360e9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -0,0 +1,265 @@
+/*
+ * 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.biometrics.sensors.fingerprint.aidl;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyFloat;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.biometrics.fingerprint.PointerContext;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlayController;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.testing.TestableContext;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+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.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.LockoutCache;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.function.Consumer;
+
+@Presubmit
+@SmallTest
+public class FingerprintAuthenticationClientTest {
+
+    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;
+    private static final float TOUCH_MAJOR = 4.4f;
+    private static final float TOUCH_MINOR = 5.5f;
+
+    @Rule
+    public final TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+
+    @Mock
+    private ISession mHal;
+    @Mock
+    private IBinder mToken;
+    @Mock
+    private ClientMonitorCallbackConverter mClientMonitorCallbackConverter;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+    @Mock
+    private LockoutCache mLockoutCache;
+    @Mock
+    private IUdfpsOverlayController mUdfpsOverlayController;
+    @Mock
+    private ISidefpsController mSideFpsController;
+    @Mock
+    private FingerprintSensorPropertiesInternal mSensorProps;
+    @Mock
+    private ClientMonitorCallback mCallback;
+    @Mock
+    private Sensor.HalSessionCallback mHalSessionCallback;
+    @Mock
+    private Probe mLuxProbe;
+    @Captor
+    private ArgumentCaptor<OperationContext> mOperationContextCaptor;
+    @Captor
+    private ArgumentCaptor<PointerContext> mPointerContextCaptor;
+
+    @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.isAoD()).thenReturn(HAS_AOD);
+    }
+
+    @Test
+    public void authNoContext_v1() throws RemoteException {
+        final FingerprintAuthenticationClient client = createClient(1);
+        client.start(mCallback);
+
+        verify(mHal).authenticate(eq(OP_ID));
+        verify(mHal, never()).authenticateWithContext(anyLong(), any());
+    }
+
+    @Test
+    public void authWithContext_v2() throws RemoteException {
+        final FingerprintAuthenticationClient client = createClient(2);
+        client.start(mCallback);
+
+        verify(mHal).authenticateWithContext(eq(OP_ID), mOperationContextCaptor.capture());
+        verify(mHal, never()).authenticate(anyLong());
+
+        final OperationContext opContext = mOperationContextCaptor.getValue();
+        assertThat(opContext.isAoD).isEqualTo(HAS_AOD);
+    }
+
+    @Test
+    public void pointerUp_v1() throws RemoteException {
+        final FingerprintAuthenticationClient client = createClient(1);
+        client.start(mCallback);
+        client.onPointerUp();
+
+        verify(mHal).onPointerUp(eq(POINTER_ID));
+        verify(mHal, never()).onPointerUpWithContext(any());
+    }
+
+    @Test
+    public void pointerDown_v1() throws RemoteException {
+        final FingerprintAuthenticationClient client = createClient(1);
+        client.start(mCallback);
+        client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
+
+        verify(mHal).onPointerDown(eq(0),
+                eq(TOUCH_X), eq(TOUCH_Y), eq(TOUCH_MAJOR), eq(TOUCH_MINOR));
+        verify(mHal, never()).onPointerDownWithContext(any());
+    }
+
+    @Test
+    public void pointerUpWithContext_v2() throws RemoteException {
+        final FingerprintAuthenticationClient client = createClient(2);
+        client.start(mCallback);
+        client.onPointerUp();
+
+        verify(mHal).onPointerUpWithContext(mPointerContextCaptor.capture());
+        verify(mHal, never()).onPointerUp(eq(POINTER_ID));
+
+        final PointerContext pContext = mPointerContextCaptor.getValue();
+        assertThat(pContext.pointerId).isEqualTo(POINTER_ID);
+    }
+
+    @Test
+    public void pointerDownWithContext_v2() throws RemoteException {
+        final FingerprintAuthenticationClient client = createClient(2);
+        client.start(mCallback);
+        client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
+
+        verify(mHal).onPointerDownWithContext(mPointerContextCaptor.capture());
+        verify(mHal, never()).onPointerDown(anyInt(), anyInt(), anyInt(), anyFloat(), anyFloat());
+
+        final PointerContext pContext = mPointerContextCaptor.getValue();
+        assertThat(pContext.pointerId).isEqualTo(POINTER_ID);
+    }
+
+    @Test
+    public void luxProbeWhenFingerDown() throws RemoteException {
+        final FingerprintAuthenticationClient 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 showHideOverlay_cancel() throws RemoteException {
+        showHideOverlay(c -> c.cancel());
+    }
+
+    @Test
+    public void showHideOverlay_stop() throws RemoteException {
+        showHideOverlay(c -> c.stopHalOperation());
+    }
+
+    @Test
+    public void showHideOverlay_error() throws RemoteException {
+        showHideOverlay(c -> c.onError(0, 0));
+        verify(mCallback).onClientFinished(any(), eq(false));
+    }
+
+    @Test
+    public void showHideOverlay_lockout() throws RemoteException {
+        showHideOverlay(c -> c.onLockoutTimed(5000));
+    }
+
+    @Test
+    public void showHideOverlay_lockoutPerm() throws RemoteException {
+        showHideOverlay(c -> c.onLockoutPermanent());
+    }
+
+    private void showHideOverlay(Consumer<FingerprintAuthenticationClient> block)
+            throws RemoteException {
+        final FingerprintAuthenticationClient client = createClient();
+
+        client.start(mCallback);
+
+        verify(mUdfpsOverlayController).showUdfpsOverlay(anyInt(), anyInt(), any());
+        verify(mSideFpsController).show(anyInt(), anyInt());
+
+        block.accept(client);
+
+        verify(mUdfpsOverlayController).hideUdfpsOverlay(anyInt());
+        verify(mSideFpsController).hide(anyInt());
+    }
+
+    private FingerprintAuthenticationClient createClient() throws RemoteException {
+        return createClient(100);
+    }
+
+    private FingerprintAuthenticationClient createClient(int version) throws RemoteException {
+        when(mHal.getInterfaceVersion()).thenReturn(version);
+
+        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+        return new FingerprintAuthenticationClient(mContext, () -> aidl, mToken,
+                2 /* requestId */, mClientMonitorCallbackConverter, 5 /* targetUserId */, OP_ID,
+        false /* restricted */, "test-owner", 4 /* cookie */, false /* requireConfirmation */,
+        9 /* sensorId */, mBiometricLogger, mBiometricContext,
+        true /* isStrongBiometric */,
+        null /* taskStackListener */, mLockoutCache,
+        mUdfpsOverlayController, mSideFpsController,
+        false /* allowBackgroundAuthentication */, mSensorProps);
+    }
+}
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
new file mode 100644
index 0000000..295fe47
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.biometrics.sensors.fingerprint.aidl;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.fingerprint.IUdfpsOverlayController;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.testing.TestableContext;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+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.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FingerprintDetectClientTest {
+
+    private static final int USER_ID = 8;
+    private static final boolean HAS_AOD = true;
+
+    @Rule
+    public final TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+
+    @Mock
+    private ISession mHal;
+    @Mock
+    private IBinder mToken;
+    @Mock
+    private ClientMonitorCallbackConverter mClientMonitorCallbackConverter;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+    @Mock
+    private IUdfpsOverlayController mUdfpsOverlayController;
+    @Mock
+    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.isAoD()).thenReturn(HAS_AOD);
+    }
+
+    @Test
+    public void detectNoContext_v1() throws RemoteException {
+        final FingerprintDetectClient client = createClient(1);
+
+        client.start(mCallback);
+
+        verify(mHal).detectInteraction();
+        verify(mHal, never()).detectInteractionWithContext(any());
+    }
+
+    @Test
+    public void detectNoContext_v2() throws RemoteException {
+        final FingerprintDetectClient client = createClient(2);
+
+        client.start(mCallback);
+
+        verify(mHal).detectInteractionWithContext(mOperationContextCaptor.capture());
+        verify(mHal, never()).detectInteraction();
+    }
+
+    private FingerprintDetectClient createClient(int version) throws RemoteException {
+        when(mHal.getInterfaceVersion()).thenReturn(version);
+
+        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+        return new FingerprintDetectClient(mContext, () -> aidl, mToken,
+                6 /* requestId */, mClientMonitorCallbackConverter, 2 /* userId */,
+                "a-test", 1 /* sensorId */, mBiometricLogger, mBiometricContext,
+                mUdfpsOverlayController, true /* isStrongBiometric */);
+    }
+}
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
new file mode 100644
index 0000000..7e44eab
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
@@ -0,0 +1,224 @@
+/*
+ * 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.biometrics.sensors.fingerprint.aidl;
+
+import static com.google.common.truth.Truth.assertThat;
+
+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.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.biometrics.fingerprint.PointerContext;
+import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlayController;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.testing.TestableContext;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.function.Consumer;
+
+@Presubmit
+@SmallTest
+public class FingerprintEnrollClientTest {
+
+    private static final byte[] HAT = new byte[69];
+    private static final int USER_ID = 8;
+    private static final int POINTER_ID = 0;
+    private static final int TOUCH_X = 8;
+    private static final int TOUCH_Y = 20;
+    private static final float TOUCH_MAJOR = 4.4f;
+    private static final float TOUCH_MINOR = 5.5f;
+
+    @Rule
+    public final TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+
+    @Mock
+    private ISession mHal;
+    @Mock
+    private IBinder mToken;
+    @Mock
+    private ClientMonitorCallbackConverter mClientMonitorCallbackConverter;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+    @Mock
+    private BiometricUtils<Fingerprint> mBiometricUtils;
+    @Mock
+    private IUdfpsOverlayController mUdfpsOverlayController;
+    @Mock
+    private ISidefpsController mSideFpsController;
+    @Mock
+    private FingerprintSensorPropertiesInternal mSensorProps;
+    @Mock
+    private ClientMonitorCallback mCallback;
+    @Mock
+    private Sensor.HalSessionCallback mHalSessionCallback;
+    @Captor
+    private ArgumentCaptor<OperationContext> mOperationContextCaptor;
+    @Captor
+    private ArgumentCaptor<PointerContext> mPointerContextCaptor;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Test
+    public void enrollNoContext_v1() throws RemoteException {
+        final FingerprintEnrollClient client = createClient(1);
+
+        client.start(mCallback);
+
+        verify(mHal).enroll(any());
+        verify(mHal, never()).enrollWithContext(any(), any());
+    }
+
+    @Test
+    public void enrollWithContext_v2() throws RemoteException {
+        final FingerprintEnrollClient client = createClient(2);
+
+        client.start(mCallback);
+
+        verify(mHal).enrollWithContext(any(), mOperationContextCaptor.capture());
+        verify(mHal, never()).enroll(any());
+    }
+
+    @Test
+    public void pointerUp_v1() throws RemoteException {
+        final FingerprintEnrollClient client = createClient(1);
+        client.start(mCallback);
+        client.onPointerUp();
+
+        verify(mHal).onPointerUp(eq(POINTER_ID));
+        verify(mHal, never()).onPointerUpWithContext(any());
+    }
+
+    @Test
+    public void pointerDown_v1() throws RemoteException {
+        final FingerprintEnrollClient client = createClient(1);
+        client.start(mCallback);
+        client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
+
+        verify(mHal).onPointerDown(eq(0),
+                eq(TOUCH_X), eq(TOUCH_Y), eq(TOUCH_MAJOR), eq(TOUCH_MINOR));
+        verify(mHal, never()).onPointerDownWithContext(any());
+    }
+
+    @Test
+    public void pointerUpWithContext_v2() throws RemoteException {
+        final FingerprintEnrollClient client = createClient(2);
+        client.start(mCallback);
+        client.onPointerUp();
+
+        verify(mHal).onPointerUpWithContext(mPointerContextCaptor.capture());
+        verify(mHal, never()).onPointerUp(eq(POINTER_ID));
+
+        final PointerContext pContext = mPointerContextCaptor.getValue();
+        assertThat(pContext.pointerId).isEqualTo(POINTER_ID);
+    }
+
+    @Test
+    public void pointerDownWithContext_v2() throws RemoteException {
+        final FingerprintEnrollClient client = createClient(2);
+        client.start(mCallback);
+        client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
+
+        verify(mHal).onPointerDownWithContext(mPointerContextCaptor.capture());
+        verify(mHal, never()).onPointerDown(anyInt(), anyInt(), anyInt(), anyFloat(), anyFloat());
+
+        final PointerContext pContext = mPointerContextCaptor.getValue();
+        assertThat(pContext.pointerId).isEqualTo(POINTER_ID);
+    }
+
+    @Test
+    public void showHideOverlay_cancel() throws RemoteException {
+        showHideOverlay(c -> c.cancel());
+    }
+
+    @Test
+    public void showHideOverlay_stop() throws RemoteException {
+        showHideOverlay(c -> c.stopHalOperation());
+    }
+
+    @Test
+    public void showHideOverlay_error() throws RemoteException {
+        showHideOverlay(c -> c.onError(0, 0));
+        verify(mCallback).onClientFinished(any(), eq(false));
+    }
+
+    @Test
+    public void showHideOverlay_result() throws RemoteException {
+        showHideOverlay(c -> c.onEnrollResult(new Fingerprint("", 1, 1), 0));
+    }
+
+    private void showHideOverlay(Consumer<FingerprintEnrollClient> block)
+            throws RemoteException {
+        final FingerprintEnrollClient client = createClient();
+
+        client.start(mCallback);
+
+        verify(mUdfpsOverlayController).showUdfpsOverlay(anyInt(), anyInt(), any());
+        verify(mSideFpsController).show(anyInt(), anyInt());
+
+        block.accept(client);
+
+        verify(mUdfpsOverlayController).hideUdfpsOverlay(anyInt());
+        verify(mSideFpsController).hide(anyInt());
+    }
+
+    private FingerprintEnrollClient createClient() throws RemoteException {
+        return createClient(500);
+    }
+
+    private FingerprintEnrollClient createClient(int version) throws RemoteException {
+        when(mHal.getInterfaceVersion()).thenReturn(version);
+
+        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+        return new FingerprintEnrollClient(mContext, () -> aidl, mToken, 6 /* requestId */,
+        mClientMonitorCallbackConverter, 0 /* userId */,
+        HAT, "owner", mBiometricUtils, 8 /* sensorId */,
+        mBiometricLogger, mBiometricContext, mSensorProps, mUdfpsOverlayController,
+        mSideFpsController, 6 /* maxTemplatesPerUser */, FingerprintManager.ENROLL_ENROLL);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
index 8b7b484..e1a4a2d 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
@@ -32,6 +32,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.CoexCoordinator;
 import com.android.server.biometrics.sensors.LockoutCache;
@@ -66,6 +68,10 @@
     private Sensor.HalSessionCallback.Callback mHalSessionCallback;
     @Mock
     private LockoutResetDispatcher mLockoutResetDispatcher;
+    @Mock
+    private BiometricLogger mLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
 
     private final TestLooper mLooper = new TestLooper();
     private final LockoutCache mLockoutCache = new LockoutCache();
@@ -102,7 +108,8 @@
 
         mScheduler.scheduleClientMonitor(new FingerprintResetLockoutClient(mContext,
                 () -> new AidlSession(1, mSession, USER_ID, mHalCallback),
-                USER_ID, TAG, SENSOR_ID, HAT, mLockoutCache, mLockoutResetDispatcher));
+                USER_ID, TAG, SENSOR_ID, mLogger, mBiometricContext, HAT, mLockoutCache,
+                mLockoutResetDispatcher));
         mLooper.dispatchAll();
 
         verifyNotLocked();
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
index ff1b6f6..83fa7ac 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
@@ -17,17 +17,23 @@
 package com.android.server.companion.virtual;
 
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.hardware.display.DisplayManagerInternal;
+import android.hardware.input.IInputManager;
+import android.hardware.input.InputManager;
 import android.hardware.input.InputManagerInternal;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.IInputConstants;
 import android.platform.test.annotations.Presubmit;
 import android.view.Display;
+import android.view.DisplayInfo;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -46,18 +52,31 @@
     @Mock
     private InputManagerInternal mInputManagerInternalMock;
     @Mock
+    private DisplayManagerInternal mDisplayManagerInternalMock;
+    @Mock
     private InputController.NativeWrapper mNativeWrapperMock;
+    @Mock
+    private IInputManager mIInputManagerMock;
 
     private InputController mInputController;
 
     @Before
-    public void setUp() {
+    public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
         doNothing().when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
         LocalServices.removeServiceForTest(InputManagerInternal.class);
         LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock);
 
+        final DisplayInfo displayInfo = new DisplayInfo();
+        displayInfo.uniqueId = "uniqueId";
+        doReturn(displayInfo).when(mDisplayManagerInternalMock).getDisplayInfo(anyInt());
+        LocalServices.removeServiceForTest(DisplayManagerInternal.class);
+        LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
+
+        InputManager.resetInstance(mIInputManagerMock);
+        doNothing().when(mIInputManagerMock).addUniqueIdAssociation(anyString(), anyString());
+        doNothing().when(mIInputManagerMock).removeUniqueIdAssociation(anyString());
         mInputController = new InputController(new Object(), mNativeWrapperMock);
     }
 
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 e36263e..ceb723a 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
@@ -25,6 +25,7 @@
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.doCallRealMethod;
 import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -57,6 +58,7 @@
 import android.platform.test.annotations.Presubmit;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.view.DisplayInfo;
 import android.view.KeyEvent;
 
 import androidx.test.InstrumentationRegistry;
@@ -80,6 +82,8 @@
     private static final int DISPLAY_ID = 2;
     private static final int PRODUCT_ID = 10;
     private static final int VENDOR_ID = 5;
+    private static final String UNIQUE_ID = "uniqueid";
+    private static final String PHYS = "phys";
     private static final int HEIGHT = 1800;
     private static final int WIDTH = 900;
     private static final Binder BINDER = new Binder("binder");
@@ -116,6 +120,12 @@
         LocalServices.removeServiceForTest(InputManagerInternal.class);
         LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock);
 
+        final DisplayInfo displayInfo = new DisplayInfo();
+        displayInfo.uniqueId = UNIQUE_ID;
+        doReturn(displayInfo).when(mDisplayManagerInternalMock).getDisplayInfo(anyInt());
+        LocalServices.removeServiceForTest(DisplayManagerInternal.class);
+        LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
+
         mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
         doNothing().when(mContext).enforceCallingOrSelfPermission(
                 eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
@@ -274,7 +284,8 @@
                 BINDER);
         assertWithMessage("Virtual keyboard should register fd when the display matches")
                 .that(mInputController.mInputDeviceDescriptors).isNotEmpty();
-        verify(mNativeWrapperMock).openUinputKeyboard(DEVICE_NAME, VENDOR_ID, PRODUCT_ID);
+        verify(mNativeWrapperMock).openUinputKeyboard(eq(DEVICE_NAME), eq(VENDOR_ID),
+                eq(PRODUCT_ID), anyString());
     }
 
     @Test
@@ -284,7 +295,8 @@
                 BINDER);
         assertWithMessage("Virtual keyboard should register fd when the display matches")
                 .that(mInputController.mInputDeviceDescriptors).isNotEmpty();
-        verify(mNativeWrapperMock).openUinputMouse(DEVICE_NAME, VENDOR_ID, PRODUCT_ID);
+        verify(mNativeWrapperMock).openUinputMouse(eq(DEVICE_NAME), eq(VENDOR_ID), eq(PRODUCT_ID),
+                anyString());
     }
 
     @Test
@@ -294,8 +306,8 @@
                 BINDER, new Point(WIDTH, HEIGHT));
         assertWithMessage("Virtual keyboard should register fd when the display matches")
                 .that(mInputController.mInputDeviceDescriptors).isNotEmpty();
-        verify(mNativeWrapperMock).openUinputTouchscreen(DEVICE_NAME, VENDOR_ID, PRODUCT_ID, HEIGHT,
-                WIDTH);
+        verify(mNativeWrapperMock).openUinputTouchscreen(eq(DEVICE_NAME), eq(VENDOR_ID),
+                eq(PRODUCT_ID), anyString(), eq(HEIGHT), eq(WIDTH));
     }
 
     @Test
@@ -315,7 +327,7 @@
         final int action = VirtualKeyEvent.ACTION_UP;
         mInputController.mInputDeviceDescriptors.put(BINDER,
                 new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 1,
-                        /* displayId= */ 1));
+                        /* displayId= */ 1, PHYS));
         mDeviceImpl.sendKeyEvent(BINDER, new VirtualKeyEvent.Builder().setKeyCode(keyCode)
                 .setAction(action).build());
         verify(mNativeWrapperMock).writeKeyEvent(fd, keyCode, action);
@@ -340,7 +352,7 @@
         final int action = VirtualMouseButtonEvent.ACTION_BUTTON_PRESS;
         mInputController.mInputDeviceDescriptors.put(BINDER,
                 new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
-                        /* displayId= */ 1));
+                        /* displayId= */ 1, PHYS));
         mInputController.mActivePointerDisplayId = 1;
         mDeviceImpl.sendButtonEvent(BINDER, new VirtualMouseButtonEvent.Builder()
                 .setButtonCode(buttonCode)
@@ -355,7 +367,7 @@
         final int action = VirtualMouseButtonEvent.ACTION_BUTTON_PRESS;
         mInputController.mInputDeviceDescriptors.put(BINDER,
                 new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
-                        /* displayId= */ 1));
+                        /* displayId= */ 1, PHYS));
         assertThrows(
                 IllegalStateException.class,
                 () ->
@@ -381,7 +393,7 @@
         final float y = 0.7f;
         mInputController.mInputDeviceDescriptors.put(BINDER,
                 new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
-                        /* displayId= */ 1));
+                        /* displayId= */ 1, PHYS));
         mInputController.mActivePointerDisplayId = 1;
         mDeviceImpl.sendRelativeEvent(BINDER, new VirtualMouseRelativeEvent.Builder()
                 .setRelativeX(x).setRelativeY(y).build());
@@ -395,7 +407,7 @@
         final float y = 0.7f;
         mInputController.mInputDeviceDescriptors.put(BINDER,
                 new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
-                        /* displayId= */ 1));
+                        /* displayId= */ 1, PHYS));
         assertThrows(
                 IllegalStateException.class,
                 () ->
@@ -422,7 +434,7 @@
         final float y = 1f;
         mInputController.mInputDeviceDescriptors.put(BINDER,
                 new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
-                        /* displayId= */ 1));
+                        /* displayId= */ 1, PHYS));
         mInputController.mActivePointerDisplayId = 1;
         mDeviceImpl.sendScrollEvent(BINDER, new VirtualMouseScrollEvent.Builder()
                 .setXAxisMovement(x)
@@ -437,7 +449,7 @@
         final float y = 1f;
         mInputController.mInputDeviceDescriptors.put(BINDER,
                 new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
-                        /* displayId= */ 1));
+                        /* displayId= */ 1, PHYS));
         assertThrows(
                 IllegalStateException.class,
                 () ->
@@ -470,7 +482,7 @@
         final int action = VirtualTouchEvent.ACTION_UP;
         mInputController.mInputDeviceDescriptors.put(BINDER,
                 new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 3,
-                        /* displayId= */ 1));
+                        /* displayId= */ 1, PHYS));
         mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder().setX(x)
                 .setY(y).setAction(action).setPointerId(pointerId).setToolType(toolType).build());
         verify(mNativeWrapperMock).writeTouchEvent(fd, pointerId, toolType, action, x, y, Float.NaN,
@@ -489,7 +501,7 @@
         final float majorAxisSize = 10.0f;
         mInputController.mInputDeviceDescriptors.put(BINDER,
                 new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 3,
-                        /* displayId= */ 1));
+                        /* displayId= */ 1, PHYS));
         mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder().setX(x)
                 .setY(y).setAction(action).setPointerId(pointerId).setToolType(toolType)
                 .setPressure(pressure).setMajorAxisSize(majorAxisSize).build());
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 564c4e4..64ce6b2 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -28,6 +28,14 @@
 import static android.app.admin.DevicePolicyManager.ID_TYPE_IMEI;
 import static android.app.admin.DevicePolicyManager.ID_TYPE_MEID;
 import static android.app.admin.DevicePolicyManager.ID_TYPE_SERIAL;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NONE;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_SYSTEM_INFO;
 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH;
 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
@@ -101,6 +109,7 @@
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
@@ -138,6 +147,9 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.devicepolicy.DevicePolicyManagerService.RestrictionsListener;
+import com.android.server.pm.RestrictionsSet;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.UserRestrictionsUtils;
 
 import org.hamcrest.BaseMatcher;
 import org.hamcrest.Description;
@@ -7764,6 +7776,296 @@
     }
 
     @Test
+    public void testSetUserRestriction_financeDo_invalidRestrictions_restrictionNotSet()
+            throws Exception {
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+        for (String restriction : UserRestrictionsUtils.USER_RESTRICTIONS) {
+            if (!UserRestrictionsUtils.canFinancedDeviceOwnerChange(restriction)) {
+                assertNoDeviceOwnerRestrictions();
+                assertExpectException(SecurityException.class, /* messageRegex= */ null,
+                        () -> dpm.addUserRestriction(admin1, restriction));
+
+                verify(getServices().userManagerInternal, never())
+                        .setDevicePolicyUserRestrictions(anyInt(), any(), any(), anyBoolean());
+            }
+        }
+    }
+
+    @Test
+    public void testSetUserRestriction_financeDo_validRestrictions_setsRestriction()
+            throws Exception {
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+        for (String restriction : UserRestrictionsUtils.USER_RESTRICTIONS) {
+            if (UserRestrictionsUtils.canFinancedDeviceOwnerChange(restriction)) {
+                assertNoDeviceOwnerRestrictions();
+                dpm.addUserRestriction(admin1, restriction);
+
+                Bundle globalRestrictions =
+                        dpms.getDeviceOwnerAdminLocked().getGlobalUserRestrictions(
+                                UserManagerInternal.OWNER_TYPE_DEVICE_OWNER);
+                RestrictionsSet localRestrictions = new RestrictionsSet();
+                localRestrictions.updateRestrictions(
+                        UserHandle.USER_SYSTEM,
+                        dpms.getDeviceOwnerAdminLocked().getLocalUserRestrictions(
+                                UserManagerInternal.OWNER_TYPE_DEVICE_OWNER));
+                verify(getServices().userManagerInternal)
+                        .setDevicePolicyUserRestrictions(eq(UserHandle.USER_SYSTEM),
+                                MockUtils.checkUserRestrictions(globalRestrictions),
+                                MockUtils.checkUserRestrictions(
+                                        UserHandle.USER_SYSTEM, localRestrictions),
+                                eq(true));
+                reset(getServices().userManagerInternal);
+
+                dpm.clearUserRestriction(admin1, restriction);
+                reset(getServices().userManagerInternal);
+            }
+        }
+    }
+
+    @Test
+    public void testSetLockTaskFeatures_financeDo_validLockTaskFeatures_lockTaskFeaturesSet()
+            throws Exception {
+        int validLockTaskFeatures = LOCK_TASK_FEATURE_SYSTEM_INFO | LOCK_TASK_FEATURE_KEYGUARD
+                | LOCK_TASK_FEATURE_HOME | LOCK_TASK_FEATURE_GLOBAL_ACTIONS
+                | LOCK_TASK_FEATURE_NOTIFICATIONS;
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+        dpm.setLockTaskFeatures(admin1, validLockTaskFeatures);
+
+        verify(getServices().iactivityTaskManager)
+                .updateLockTaskFeatures(eq(UserHandle.USER_SYSTEM), eq(validLockTaskFeatures));
+    }
+
+    @Test
+    public void testSetLockTaskFeatures_financeDo_invalidLockTaskFeatures_throwsException()
+            throws Exception {
+        int invalidLockTaskFeatures = LOCK_TASK_FEATURE_NONE | LOCK_TASK_FEATURE_OVERVIEW
+                | LOCK_TASK_FEATURE_HOME | LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK;
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+        // Called during setup.
+        verify(getServices().iactivityTaskManager).updateLockTaskFeatures(anyInt(), anyInt());
+
+        assertExpectException(SecurityException.class, /* messageRegex= */ null,
+                () -> dpm.setLockTaskFeatures(admin1, invalidLockTaskFeatures));
+
+        verifyNoMoreInteractions(getServices().iactivityTaskManager);
+    }
+
+    @Test
+    public void testIsUninstallBlocked_financeDo_success() throws Exception {
+        String packageName = "com.android.foo.package";
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+        when(getServices().ipackageManager.getBlockUninstallForUser(
+                eq(packageName), eq(UserHandle.USER_SYSTEM)))
+                .thenReturn(true);
+
+        assertThat(dpm.isUninstallBlocked(admin1, packageName)).isTrue();
+    }
+
+    @Test
+    public void testSetUninstallBlocked_financeDo_success() throws Exception {
+        String packageName = "com.android.foo.package";
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+        dpm.setUninstallBlocked(admin1, packageName, false);
+
+        verify(getServices().ipackageManager)
+                .setBlockUninstallForUser(eq(packageName), eq(false),
+                        eq(UserHandle.USER_SYSTEM));
+    }
+
+    @Test
+    public void testSetUserControlDisabledPackages_financeDo_success() throws Exception {
+        List<String> packages = new ArrayList<>();
+        packages.add("com.android.foo.package");
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+        dpm.setUserControlDisabledPackages(admin1, packages);
+
+        verify(getServices().packageManagerInternal)
+                .setDeviceOwnerProtectedPackages(eq(admin1.getPackageName()), eq(packages));
+    }
+
+    @Test
+    public void testGetUserControlDisabledPackages_financeDo_success() throws Exception {
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+        assertThat(dpm.getUserControlDisabledPackages(admin1)).isEmpty();
+    }
+
+    @Test
+    public void testSetOrganizationName_financeDo_success() throws Exception {
+        String organizationName = "Test Organization";
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+        dpm.setOrganizationName(admin1, organizationName);
+
+        assertThat(dpm.getDeviceOwnerOrganizationName()).isEqualTo(organizationName);
+    }
+
+    @Test
+    public void testSetShortSupportMessage_financeDo_success() throws Exception {
+        String supportMessage = "Test short support message";
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+        dpm.setShortSupportMessage(admin1, supportMessage);
+
+        assertThat(dpm.getShortSupportMessage(admin1)).isEqualTo(supportMessage);
+    }
+
+    @Test
+    public void testIsBackupServiceEnabled_financeDo_success() throws Exception {
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+        when(getServices().ibackupManager.isBackupServiceActive(eq(UserHandle.USER_SYSTEM)))
+                .thenReturn(true);
+
+        assertThat(dpm.isBackupServiceEnabled(admin1)).isTrue();
+    }
+
+    @Test
+    public void testSetBackupServiceEnabled_financeDo_success() throws Exception {
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+        dpm.setBackupServiceEnabled(admin1, true);
+
+        verify(getServices().ibackupManager)
+                .setBackupServiceActive(eq(UserHandle.USER_SYSTEM), eq(true));
+    }
+
+    @Test
+    public void testIsLockTaskPermitted_financeDo_success() throws Exception {
+        String packageName = "com.android.foo.package";
+        mockPolicyExemptApps(packageName);
+        mockVendorPolicyExemptApps();
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+        assertThat(dpm.isLockTaskPermitted(packageName)).isTrue();
+    }
+
+    @Test
+    public void testSetLockTaskPackages_financeDo_success() throws Exception {
+        String[] packages = {"com.android.foo.package"};
+        mockEmptyPolicyExemptApps();
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+        dpm.setLockTaskPackages(admin1, packages);
+
+        verify(getServices().iactivityManager)
+                .updateLockTaskPackages(eq(UserHandle.USER_SYSTEM), eq(packages));
+    }
+
+    @Test
+    public void testAddPersistentPreferredActivity_financeDo_success() throws Exception {
+        IntentFilter filter = new IntentFilter();
+        ComponentName target = new ComponentName(admin2.getPackageName(), "test.class");
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+        dpm.addPersistentPreferredActivity(admin1, filter, target);
+
+        verify(getServices().ipackageManager)
+                .addPersistentPreferredActivity(eq(filter), eq(target), eq(UserHandle.USER_SYSTEM));
+        verify(getServices().ipackageManager)
+                .flushPackageRestrictionsAsUser(eq(UserHandle.USER_SYSTEM));
+    }
+
+    @Test
+    public void testClearPackagePersistentPreferredActvities_financeDo_success() throws Exception {
+        String packageName = admin2.getPackageName();
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+        dpm.clearPackagePersistentPreferredActivities(admin1, packageName);
+
+        verify(getServices().ipackageManager)
+                .clearPackagePersistentPreferredActivities(
+                        eq(packageName), eq(UserHandle.USER_SYSTEM));
+        verify(getServices().ipackageManager)
+                .flushPackageRestrictionsAsUser(eq(UserHandle.USER_SYSTEM));
+    }
+
+    @Test
+    public void testWipeData_financeDo_success() throws Exception {
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+        when(getServices().userManager.getUserRestrictionSource(
+                UserManager.DISALLOW_FACTORY_RESET,
+                UserHandle.SYSTEM))
+                .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER);
+        when(mMockContext.getResources()
+                .getString(R.string.work_profile_deleted_description_dpm_wipe))
+                .thenReturn("Test string");
+
+        dpm.wipeData(0);
+
+        verifyRebootWipeUserData(/* wipeEuicc= */ false);
+    }
+
+    @Test
+    public void testIsDeviceOwnerApp_financeDo_success() throws Exception {
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+        assertThat(dpm.isDeviceOwnerApp(admin1.getPackageName())).isTrue();
+    }
+
+    @Test
+    public void testClearDeviceOwnerApp_financeDo_success() throws Exception {
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+        dpm.clearDeviceOwnerApp(admin1.getPackageName());
+
+        assertThat(dpm.getDeviceOwnerComponentOnAnyUser()).isNull();
+        assertThat(dpm.isAdminActiveAsUser(admin1, UserHandle.USER_SYSTEM)).isFalse();
+        verify(mMockContext.spiedContext, times(2))
+                .sendBroadcastAsUser(
+                        MockUtils.checkIntentAction(
+                                DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED),
+                        eq(UserHandle.SYSTEM));
+    }
+
+    @Test
+    public void testSetPermissionGrantState_financeDo_notReadPhoneStatePermission_throwsException()
+            throws Exception {
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+        assertExpectException(SecurityException.class, /* messageRegex= */ null,
+                () -> dpm.setPermissionGrantState(admin1, admin1.getPackageName(),
+                        permission.READ_CALENDAR,
+                        DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED));
+    }
+
+    @Test
+    public void testSetPermissionGrantState_financeDo_grantPermissionToNonDeviceOwnerPackage_throwsException()
+            throws Exception {
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+        assertExpectException(SecurityException.class, /* messageRegex= */ null,
+                () -> dpm.setPermissionGrantState(admin1, "com.android.foo.package",
+                        permission.READ_PHONE_STATE,
+                        DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED));
+    }
+
+    @Test
     public void testSetUsbDataSignalingEnabled_noDeviceOwnerOrPoOfOrgOwnedDevice() {
         assertThrows(SecurityException.class,
                 () -> dpm.setUsbDataSignalingEnabled(true));
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
index 15f3ed1..4cb46b4 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
@@ -109,6 +109,14 @@
     public static Bundle checkUserRestrictions(String... keys) {
         final Bundle expected = DpmTestUtils.newRestrictions(
                 java.util.Objects.requireNonNull(keys));
+        return checkUserRestrictions(expected);
+    }
+
+    public static Bundle checkUserRestrictions(Bundle expected) {
+        return createUserRestrictionsBundleMatcher(expected);
+    }
+
+    private static Bundle createUserRestrictionsBundleMatcher(Bundle expected) {
         final Matcher<Bundle> m = new BaseMatcher<Bundle>() {
             @Override
             public boolean matches(Object item) {
@@ -129,6 +137,15 @@
     public static RestrictionsSet checkUserRestrictions(int userId, String... keys) {
         final RestrictionsSet expected = DpmTestUtils.newRestrictions(userId,
                 java.util.Objects.requireNonNull(keys));
+        return checkUserRestrictions(userId, expected);
+    }
+
+    public static RestrictionsSet checkUserRestrictions(int userId, RestrictionsSet expected) {
+        return createUserRestrictionsSetMatcher(userId, expected);
+    }
+
+    private static RestrictionsSet createUserRestrictionsSetMatcher(
+            int userId, RestrictionsSet expected) {
         final Matcher<RestrictionsSet> m = new BaseMatcher<RestrictionsSet>() {
             @Override
             public boolean matches(Object item) {
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
index a8f24ce..533fb2d 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
@@ -26,6 +26,7 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.when;
 
@@ -76,6 +77,7 @@
     private static final ComponentName TEST_MDM_COMPONENT_NAME = new ComponentName(
             TEST_DPC_PACKAGE_NAME, "pc.package.name.DeviceAdmin");
     private static final int TEST_USER_ID = 123;
+    private static final String ROLE_HOLDER_PACKAGE_NAME = "test.role.holder.package.name";
 
     private @Mock Resources mResources;
 
@@ -305,6 +307,26 @@
                 ACTION_PROVISION_MANAGED_PROFILE, "package1", "package2", "package3");
     }
 
+    @Test
+    public void testGetNonRequiredApps_managedProfile_roleHolder_works() {
+        when(mInjector.getDeviceManagerRoleHolderPackageName(any()))
+                .thenReturn(ROLE_HOLDER_PACKAGE_NAME);
+        setSystemAppsWithLauncher("package1", "package2", ROLE_HOLDER_PACKAGE_NAME);
+
+        verifyAppsAreNonRequired(
+                ACTION_PROVISION_MANAGED_PROFILE, "package1", "package2");
+    }
+
+    @Test
+    public void testGetNonRequiredApps_managedDevice_roleHolder_works() {
+        when(mInjector.getDeviceManagerRoleHolderPackageName(any()))
+                .thenReturn(ROLE_HOLDER_PACKAGE_NAME);
+        setSystemAppsWithLauncher("package1", "package2", ROLE_HOLDER_PACKAGE_NAME);
+
+        verifyAppsAreNonRequired(
+                ACTION_PROVISION_MANAGED_DEVICE, "package1", "package2");
+    }
+
     private void setupRegularModulesWithManagedUser(String... regularModules) {
         setupRegularModulesWithMetadata(regularModules, REQUIRED_APP_MANAGED_USER);
     }
diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
index b7af010..6203c2f 100644
--- a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
@@ -663,6 +663,30 @@
             eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_HDR_PLAYING));
     }
 
+    @Test
+    public void tetHbmStats_LowRequestedBrightness() {
+        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
+        final int displayStatsId = mDisplayUniqueId.hashCode();
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
+        advanceTime(0);
+        // verify in HBM sunlight mode
+        assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
+        // verify HBM_ON_SUNLIGHT
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
+            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
+
+        hbmcOnBrightnessChanged(hbmc, DEFAULT_MIN);
+        // verify HBM_SV_OFF due to LOW_REQUESTED_BRIGHTNESS
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
+            eq(FrameworkStatsLog
+                      .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LOW_REQUESTED_BRIGHTNESS));
+    }
+
     private void assertState(HighBrightnessModeController hbmc,
             float brightnessMin, float brightnessMax, int hbmMode) {
         assertEquals(brightnessMin, hbmc.getCurrentBrightnessMin(), EPSILON);
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index e80721a..94cf20f 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -51,10 +51,9 @@
 import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
 import static android.net.NetworkStats.METERED_NO;
 import static android.net.NetworkStats.METERED_YES;
+import static android.net.NetworkTemplate.MATCH_CARRIER;
 import static android.net.NetworkTemplate.MATCH_MOBILE;
-import static android.net.NetworkTemplate.buildTemplateCarrierMetered;
-import static android.net.NetworkTemplate.buildTemplateWifi;
-import static android.net.TrafficStats.MB_IN_BYTES;
+import static android.net.NetworkTemplate.MATCH_WIFI;
 import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
 import static android.telephony.CarrierConfigManager.DATA_CYCLE_THRESHOLD_DISABLED;
 import static android.telephony.CarrierConfigManager.DATA_CYCLE_USE_PLATFORM_DEFAULT;
@@ -205,6 +204,7 @@
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.TimeZone;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
@@ -229,10 +229,12 @@
     private static final int TEST_SUB_ID = 42;
     private static final Network TEST_NETWORK = mock(Network.class, CALLS_REAL_METHODS);
 
-
-    private static NetworkTemplate sTemplateWifi = buildTemplateWifi(TEST_WIFI_NETWORK_KEY);
+    private static NetworkTemplate sTemplateWifi = new NetworkTemplate.Builder(MATCH_WIFI)
+            .setWifiNetworkKeys(Set.of(TEST_WIFI_NETWORK_KEY)).build();
     private static NetworkTemplate sTemplateCarrierMetered =
-            buildTemplateCarrierMetered(TEST_IMSI);
+            new NetworkTemplate.Builder(MATCH_CARRIER)
+                    .setSubscriberIds(Set.of(TEST_IMSI))
+                    .setMeteredness(METERED_YES).build();
 
     /**
      * Path on assets where files used by {@link NetPolicyXml} are located.
@@ -1160,11 +1162,12 @@
 
         mPolicyListener.expect().onMeteredIfacesChanged(any());
         setNetworkPolicies(new NetworkPolicy(
-                sTemplateWifi, CYCLE_DAY, TIMEZONE_UTC, 1 * MB_IN_BYTES, 2 * MB_IN_BYTES, false));
+                sTemplateWifi, CYCLE_DAY, TIMEZONE_UTC, DataUnit.MEBIBYTES.toBytes(1),
+                DataUnit.MEBIBYTES.toBytes(2), false));
         mPolicyListener.waitAndVerify().onMeteredIfacesChanged(eq(new String[]{TEST_IFACE}));
 
         verify(mNetworkManager, atLeastOnce()).setInterfaceQuota(TEST_IFACE,
-                (2 * MB_IN_BYTES) - 512);
+                DataUnit.MEBIBYTES.toBytes(2) - 512);
     }
 
     @Test
@@ -1252,7 +1255,7 @@
             reset(mTelephonyManager, mNetworkManager, mNotifManager);
             TelephonyManager tmSub = expectMobileDefaults();
 
-            mService.snoozeLimit(NetworkTemplate.buildTemplateCarrierMetered(TEST_IMSI));
+            mService.snoozeLimit(sTemplateCarrierMetered);
             mService.updateNetworks();
 
             verify(tmSub, atLeastOnce()).setPolicyDataEnabled(true);
@@ -1955,7 +1958,7 @@
         assertEquals("Unexpected number of network policies", 1, policies.length);
         NetworkPolicy actualPolicy = policies[0];
         assertEquals("Unexpected template match rule in network policies",
-                NetworkTemplate.MATCH_WIFI,
+                MATCH_WIFI,
                 actualPolicy.template.getMatchRule());
         assertEquals("Unexpected subscriberIds size in network policies",
                 actualPolicy.template.getSubscriberIds().size(), 0);
@@ -2026,7 +2029,10 @@
 
     private static NetworkPolicy buildFakeCarrierPolicy(int cycleDay, long warningBytes,
             long limitBytes, boolean inferred) {
-        final NetworkTemplate template = buildTemplateCarrierMetered(FAKE_SUBSCRIBER_ID);
+        // TODO: Refactor this to use sTemplateCarrierMetered.
+        final NetworkTemplate template = new NetworkTemplate.Builder(MATCH_CARRIER)
+                .setSubscriberIds(Set.of(FAKE_SUBSCRIBER_ID))
+                .setMeteredness(METERED_YES).build();
         return new NetworkPolicy(template, cycleDay, TimeZone.getDefault().getID(), warningBytes,
                 limitBytes, SNOOZE_NEVER, SNOOZE_NEVER, true, inferred);
     }
diff --git a/services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java b/services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java
new file mode 100644
index 0000000..4ae9613
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java
@@ -0,0 +1,410 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power;
+
+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.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.AlarmManager;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.IPowerManager;
+import android.os.PowerManager;
+import android.os.PowerManagerInternal;
+import android.os.test.TestLooper;
+import android.provider.Settings;
+import android.test.mock.MockContentResolver;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.util.test.BroadcastInterceptingContext;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.server.LocalServices;
+import com.android.server.testutils.OffsettableClock;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for {@link com.android.server.power.LowPowerStandbyController}.
+ *
+ * Build/Install/Run:
+ * atest LowPowerStandbyControllerTest
+ */
+public class LowPowerStandbyControllerTest {
+    private static final int STANDBY_TIMEOUT = 5000;
+
+    private LowPowerStandbyController mController;
+    private BroadcastInterceptingContext mContextSpy;
+    private Resources mResourcesSpy;
+    private OffsettableClock mClock;
+    private TestLooper mTestLooper;
+
+    @Mock
+    private AlarmManager mAlarmManagerMock;
+    @Mock
+    private IPowerManager mIPowerManagerMock;
+    @Mock
+    private PowerManagerInternal mPowerManagerInternalMock;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mContextSpy = spy(new BroadcastInterceptingContext(InstrumentationRegistry.getContext()));
+        when(mContextSpy.getSystemService(AlarmManager.class)).thenReturn(mAlarmManagerMock);
+        PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock, null, null);
+        when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager);
+        addLocalServiceMock(PowerManagerInternal.class, mPowerManagerInternalMock);
+
+        when(mIPowerManagerMock.isInteractive()).thenReturn(true);
+
+        mResourcesSpy = spy(mContextSpy.getResources());
+        when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
+        when(mResourcesSpy.getBoolean(
+                com.android.internal.R.bool.config_lowPowerStandbySupported))
+                .thenReturn(true);
+        when(mResourcesSpy.getInteger(
+                com.android.internal.R.integer.config_lowPowerStandbyNonInteractiveTimeout))
+                .thenReturn(STANDBY_TIMEOUT);
+        when(mResourcesSpy.getBoolean(
+                com.android.internal.R.bool.config_lowPowerStandbyEnabledByDefault))
+                .thenReturn(false);
+
+        FakeSettingsProvider.clearSettingsProvider();
+        MockContentResolver cr = new MockContentResolver(mContextSpy);
+        cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+        when(mContextSpy.getContentResolver()).thenReturn(cr);
+
+        mClock = new OffsettableClock.Stopped();
+        mTestLooper = new TestLooper(mClock::now);
+
+        mController = new LowPowerStandbyController(mContextSpy, mTestLooper.getLooper(),
+                () -> mClock.now());
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        LocalServices.removeServiceForTest(PowerManagerInternal.class);
+        LocalServices.removeServiceForTest(LowPowerStandbyControllerInternal.class);
+    }
+
+    @Test
+    public void testOnSystemReady_isInactivate() {
+        setLowPowerStandbySupportedConfig(true);
+        mController.systemReady();
+
+        assertThat(mController.isActive()).isFalse();
+        verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean());
+    }
+
+    @Test
+    public void testActivate() throws Exception {
+        setLowPowerStandbySupportedConfig(true);
+        mController.systemReady();
+        mController.setEnabled(true);
+        setNonInteractive();
+        setDeviceIdleMode(true);
+        awaitStandbyTimeoutAlarm();
+        assertThat(mController.isActive()).isTrue();
+        verify(mPowerManagerInternalMock, times(1)).setLowPowerStandbyActive(true);
+    }
+
+    private void awaitStandbyTimeoutAlarm() {
+        ArgumentCaptor<Long> timeArg = ArgumentCaptor.forClass(Long.class);
+        ArgumentCaptor<AlarmManager.OnAlarmListener> listenerArg =
+                ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
+        verify(mAlarmManagerMock).setExact(
+                eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
+                timeArg.capture(), anyString(),
+                listenerArg.capture(), any());
+        mClock.reset();
+        mClock.fastForward(timeArg.getValue());
+        listenerArg.getValue().onAlarm();
+        mTestLooper.dispatchAll();
+    }
+
+    @Test
+    public void testOnNonInteractive_notImmediatelyActive() throws Exception {
+        setLowPowerStandbySupportedConfig(true);
+        mController.systemReady();
+        mController.setEnabled(true);
+
+        setNonInteractive();
+        mTestLooper.dispatchAll();
+
+        assertThat(mController.isActive()).isFalse();
+        verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean());
+    }
+
+    @Test
+    public void testOnNonInteractive_activateAfterStandbyTimeout() throws Exception {
+        setLowPowerStandbySupportedConfig(true);
+        mController.systemReady();
+        mController.setEnabled(true);
+
+        setNonInteractive();
+        awaitStandbyTimeoutAlarm();
+
+        assertThat(mController.isActive()).isTrue();
+        verify(mPowerManagerInternalMock, times(1)).setLowPowerStandbyActive(true);
+    }
+
+    @Test
+    public void testOnNonInteractive_doesNotActivateWhenBecomingInteractive() throws Exception {
+        setLowPowerStandbySupportedConfig(true);
+        mController.systemReady();
+        mController.setEnabled(true);
+
+        setNonInteractive();
+        advanceTime(STANDBY_TIMEOUT / 2);
+        setInteractive();
+        verifyStandbyAlarmCancelled();
+
+        assertThat(mController.isActive()).isFalse();
+        verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean());
+    }
+
+    private void verifyStandbyAlarmCancelled() {
+        InOrder inOrder = inOrder(mAlarmManagerMock);
+        inOrder.verify(mAlarmManagerMock, atLeast(0)).setExact(anyInt(), anyLong(), anyString(),
+                any(), any());
+        inOrder.verify(mAlarmManagerMock).cancel((AlarmManager.OnAlarmListener) any());
+        inOrder.verifyNoMoreInteractions();
+    }
+
+    @Test
+    public void testOnInteractive_deactivate() throws Exception {
+        setLowPowerStandbySupportedConfig(true);
+        mController.systemReady();
+        mController.setEnabled(true);
+        setNonInteractive();
+        setDeviceIdleMode(true);
+        awaitStandbyTimeoutAlarm();
+
+        setInteractive();
+        mTestLooper.dispatchAll();
+
+        assertThat(mController.isActive()).isFalse();
+        verify(mPowerManagerInternalMock, times(1)).setLowPowerStandbyActive(false);
+    }
+
+    @Test
+    public void testOnDozeMaintenance_deactivate() throws Exception {
+        setLowPowerStandbySupportedConfig(true);
+        mController.systemReady();
+        mController.setEnabled(true);
+        mController.setActiveDuringMaintenance(false);
+        setNonInteractive();
+        setDeviceIdleMode(true);
+        awaitStandbyTimeoutAlarm();
+
+        setDeviceIdleMode(false);
+        mTestLooper.dispatchAll();
+
+        assertThat(mController.isActive()).isFalse();
+        verify(mPowerManagerInternalMock, times(1)).setLowPowerStandbyActive(false);
+    }
+
+    @Test
+    public void testOnDozeMaintenance_activeDuringMaintenance_staysActive() throws Exception {
+        setLowPowerStandbySupportedConfig(true);
+        mController.systemReady();
+        mController.setEnabled(true);
+        mController.setActiveDuringMaintenance(true);
+        setNonInteractive();
+        setDeviceIdleMode(true);
+        awaitStandbyTimeoutAlarm();
+
+        setDeviceIdleMode(false);
+        mTestLooper.dispatchAll();
+
+        assertThat(mController.isActive()).isTrue();
+        verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(false);
+    }
+
+    @Test
+    public void testOnDozeMaintenanceEnds_activate() throws Exception {
+        setLowPowerStandbySupportedConfig(true);
+        mController.systemReady();
+        mController.setEnabled(true);
+        setNonInteractive();
+        setDeviceIdleMode(true);
+        awaitStandbyTimeoutAlarm();
+
+        setDeviceIdleMode(false);
+        advanceTime(1000);
+        setDeviceIdleMode(true);
+        mTestLooper.dispatchAll();
+
+        assertThat(mController.isActive()).isTrue();
+        verify(mPowerManagerInternalMock, times(2)).setLowPowerStandbyActive(true);
+    }
+
+    @Test
+    public void testLowPowerStandbyDisabled_doesNotActivate() throws Exception {
+        setLowPowerStandbySupportedConfig(true);
+        mController.systemReady();
+        mController.setEnabled(false);
+        setNonInteractive();
+
+        assertThat(mController.isActive()).isFalse();
+        verify(mAlarmManagerMock, never()).setExact(anyInt(), anyLong(), anyString(), any(), any());
+        verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean());
+    }
+
+    @Test
+    public void testLowPowerStandbyEnabled_EnabledChangedBroadcastsAreSent() throws Exception {
+        setLowPowerStandbySupportedConfig(true);
+        mController.systemReady();
+
+        BroadcastInterceptingContext.FutureIntent futureIntent = mContextSpy.nextBroadcastIntent(
+                PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED);
+        mController.setEnabled(false);
+        futureIntent.assertNotReceived();
+
+        futureIntent = mContextSpy.nextBroadcastIntent(
+                PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED);
+        mController.setEnabled(true);
+        assertThat(futureIntent.get(1, TimeUnit.SECONDS)).isNotNull();
+
+        futureIntent = mContextSpy.nextBroadcastIntent(
+                PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED);
+        mController.setEnabled(true);
+        futureIntent.assertNotReceived();
+
+        futureIntent = mContextSpy.nextBroadcastIntent(
+                PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED);
+
+        mController.setEnabled(false);
+        assertThat(futureIntent.get(1, TimeUnit.SECONDS)).isNotNull();
+    }
+
+    @Test
+    public void testSetEnabled_WhenNotSupported_DoesNotEnable() throws Exception {
+        setLowPowerStandbySupportedConfig(false);
+        mController.systemReady();
+
+        mController.setEnabled(true);
+
+        assertThat(mController.isEnabled()).isFalse();
+    }
+
+    @Test
+    public void testIsSupported_WhenSupported() throws Exception {
+        setLowPowerStandbySupportedConfig(true);
+        mController.systemReady();
+
+        assertThat(mController.isSupported()).isTrue();
+    }
+
+    @Test
+    public void testIsSupported_WhenNotSupported() throws Exception {
+        setLowPowerStandbySupportedConfig(false);
+        mController.systemReady();
+
+        assertThat(mController.isSupported()).isFalse();
+    }
+
+    @Test
+    public void testAllowlistChange_servicesAreNotified() throws Exception {
+        setLowPowerStandbySupportedConfig(true);
+        mController.systemReady();
+
+        LowPowerStandbyControllerInternal service = LocalServices.getService(
+                LowPowerStandbyControllerInternal.class);
+        service.addToAllowlist(10);
+        mTestLooper.dispatchAll();
+        verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[] {10});
+
+        service.removeFromAllowlist(10);
+        mTestLooper.dispatchAll();
+        verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[] {});
+    }
+
+    @Test
+    public void testForceActive() throws Exception {
+        setLowPowerStandbySupportedConfig(false);
+        mController.systemReady();
+
+        mController.forceActive(true);
+        mTestLooper.dispatchAll();
+
+        assertThat(mController.isActive()).isTrue();
+        verify(mPowerManagerInternalMock).setLowPowerStandbyActive(true);
+
+        mController.forceActive(false);
+        mTestLooper.dispatchAll();
+
+        assertThat(mController.isActive()).isFalse();
+        verify(mPowerManagerInternalMock).setLowPowerStandbyActive(false);
+    }
+
+    private void setLowPowerStandbySupportedConfig(boolean supported) {
+        when(mResourcesSpy.getBoolean(
+                com.android.internal.R.bool.config_lowPowerStandbySupported))
+                .thenReturn(supported);
+    }
+
+    private void setInteractive() throws Exception {
+        when(mIPowerManagerMock.isInteractive()).thenReturn(true);
+        mContextSpy.sendBroadcast(new Intent(Intent.ACTION_SCREEN_ON));
+    }
+
+    private void setNonInteractive() throws Exception {
+        when(mIPowerManagerMock.isInteractive()).thenReturn(false);
+        mContextSpy.sendBroadcast(new Intent(Intent.ACTION_SCREEN_OFF));
+    }
+
+    private void setDeviceIdleMode(boolean idle) throws Exception {
+        when(mIPowerManagerMock.isDeviceIdleMode()).thenReturn(idle);
+        mContextSpy.sendBroadcast(new Intent(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED));
+    }
+
+    private void advanceTime(long timeMs) {
+        mClock.fastForward(timeMs);
+        mTestLooper.dispatchAll();
+    }
+
+    /**
+     * Creates a mock and registers it to {@link LocalServices}.
+     */
+    private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
+        LocalServices.removeServiceForTest(clazz);
+        LocalServices.addService(clazz, mock);
+    }
+}
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 d35c679..51dbd97 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.power;
 
+import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
+import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
 import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP;
 import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE;
 import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING;
@@ -65,6 +67,7 @@
 import android.os.Looper;
 import android.os.PowerManager;
 import android.os.PowerSaveState;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.test.TestLooper;
 import android.provider.Settings;
@@ -86,6 +89,7 @@
 import com.android.server.power.PowerManagerService.Injector;
 import com.android.server.power.PowerManagerService.NativeWrapper;
 import com.android.server.power.PowerManagerService.UserSwitchedReceiver;
+import com.android.server.power.PowerManagerService.WakeLock;
 import com.android.server.power.batterysaver.BatterySaverController;
 import com.android.server.power.batterysaver.BatterySaverPolicy;
 import com.android.server.power.batterysaver.BatterySaverStateMachine;
@@ -283,6 +287,13 @@
             void invalidateIsInteractiveCaches() {
                 // Avoids an SELinux failure.
             }
+
+            @Override
+            LowPowerStandbyController createLowPowerStandbyController(Context context,
+                    Looper looper) {
+                return new LowPowerStandbyController(context, mTestLooper.getLooper(),
+                        SystemClock::elapsedRealtime);
+            }
         });
         return mService;
     }
@@ -293,6 +304,7 @@
         LocalServices.removeServiceForTest(DisplayManagerInternal.class);
         LocalServices.removeServiceForTest(BatteryManagerInternal.class);
         LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+        LocalServices.removeServiceForTest(LowPowerStandbyControllerInternal.class);
         FakeSettingsProvider.clearSettingsProvider();
     }
 
@@ -1575,4 +1587,60 @@
                 .setFullPowerSavePolicy(mockSetPolicyConfig)).isTrue();
         verify(mBatterySaverStateMachineMock).setFullBatterySaverPolicy(eq(mockSetPolicyConfig));
     }
+
+    @Test
+    public void testLowPowerStandby_whenInactive_FgsWakeLockEnabled() {
+        createService();
+        mService.systemReady();
+        WakeLock wakeLock = acquireWakeLock("fgsWakeLock", PowerManager.PARTIAL_WAKE_LOCK);
+        mService.updateUidProcStateInternal(wakeLock.mOwnerUid, PROCESS_STATE_FOREGROUND_SERVICE);
+        mService.setDeviceIdleModeInternal(true);
+
+        assertThat(wakeLock.mDisabled).isFalse();
+    }
+
+    @Test
+    public void testLowPowerStandby_whenActive_FgsWakeLockDisabled() {
+        createService();
+        mService.systemReady();
+        WakeLock wakeLock = acquireWakeLock("fgsWakeLock", PowerManager.PARTIAL_WAKE_LOCK);
+        mService.updateUidProcStateInternal(wakeLock.mOwnerUid, PROCESS_STATE_FOREGROUND_SERVICE);
+        mService.setDeviceIdleModeInternal(true);
+        mService.setLowPowerStandbyActiveInternal(true);
+
+        assertThat(wakeLock.mDisabled).isTrue();
+    }
+
+    @Test
+    public void testLowPowerStandby_whenActive_FgsWakeLockEnabledIfAllowlisted() {
+        createService();
+        mService.systemReady();
+        WakeLock wakeLock = acquireWakeLock("fgsWakeLock", PowerManager.PARTIAL_WAKE_LOCK);
+        mService.updateUidProcStateInternal(wakeLock.mOwnerUid, PROCESS_STATE_FOREGROUND_SERVICE);
+        mService.setDeviceIdleModeInternal(true);
+        mService.setLowPowerStandbyActiveInternal(true);
+        mService.setLowPowerStandbyAllowlistInternal(new int[]{wakeLock.mOwnerUid});
+
+        assertThat(wakeLock.mDisabled).isFalse();
+    }
+
+    @Test
+    public void testLowPowerStandby_whenActive_BoundTopWakeLockDisabled() {
+        createService();
+        mService.systemReady();
+        WakeLock wakeLock = acquireWakeLock("BoundTopWakeLock", PowerManager.PARTIAL_WAKE_LOCK);
+        mService.updateUidProcStateInternal(wakeLock.mOwnerUid, PROCESS_STATE_BOUND_TOP);
+        mService.setDeviceIdleModeInternal(true);
+        mService.setLowPowerStandbyActiveInternal(true);
+
+        assertThat(wakeLock.mDisabled).isFalse();
+    }
+
+    private WakeLock acquireWakeLock(String tag, int flags) {
+        IBinder token = new Binder();
+        String packageName = "pkg.name";
+        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY);
+        return mService.findWakeLockLocked(token);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/power/WakeLockLogTest.java b/services/tests/servicestests/src/com/android/server/power/WakeLockLogTest.java
index 09612e3..a73fcb8 100644
--- a/services/tests/servicestests/src/com/android/server/power/WakeLockLogTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/WakeLockLogTest.java
@@ -211,6 +211,25 @@
                 dumpLog(log, false));
     }
 
+    @Test
+    public void testAddSystemWakelock() {
+        final int tagDatabaseSize = 6;
+        final int logSize = 10;
+        TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+        WakeLockLog log = new WakeLockLog(injectorSpy);
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
+        log.onWakeLockAcquired("TagPartial", 101,
+                PowerManager.PARTIAL_WAKE_LOCK | PowerManager.SYSTEM_WAKELOCK);
+
+        assertEquals("Wake Lock Log\n"
+                        + "  01-01 00:00:01.000 - 101 - ACQ TagPartial (partial,system-wakelock)\n"
+                        + "  -\n"
+                        + "  Events: 1, Time-Resets: 0\n"
+                        + "  Buffer, Bytes used: 3\n",
+                dumpLog(log, false));
+    }
+
     private String dumpLog(WakeLockLog log, boolean includeTagDb) {
         StringWriter sw = new StringWriter();
         PrintWriter pw = new PrintWriter(sw);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
index 5eed30b..91d4f8f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
@@ -67,6 +67,7 @@
     @Mock Vibrator mVibrator;
 
     private final String callPkg = "com.android.server.notification";
+    private final String sysPkg = "android";
     private final int callUid = 10;
     private String smsPkg;
     private final int smsUid = 11;
@@ -79,6 +80,7 @@
     private NotificationRecord mRecordHighCall;
     private NotificationRecord mRecordHighCallStyle;
     private NotificationRecord mRecordEmail;
+    private NotificationRecord mRecordSystemMax;
     private NotificationRecord mRecordInlineReply;
     private NotificationRecord mRecordSms;
     private NotificationRecord mRecordStarredContact;
@@ -191,6 +193,12 @@
         mRecordContact.setContactAffinity(ValidateNotificationPeople.VALID_CONTACT);
         mRecordContact.setSystemImportance(NotificationManager.IMPORTANCE_DEFAULT);
 
+        Notification nSystemMax = new Notification.Builder(mContext, TEST_CHANNEL_ID).build();
+        mRecordSystemMax = new NotificationRecord(mContext, new StatusBarNotification(sysPkg,
+                sysPkg, 1, "systemmax", uid2, uid2, nSystemMax, new UserHandle(userId),
+                "", 1244), getDefaultChannel());
+        mRecordSystemMax.setSystemImportance(NotificationManager.IMPORTANCE_HIGH);
+
         Notification n8 = new Notification.Builder(mContext, TEST_CHANNEL_ID).build();
         mRecordUrgent = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
                 pkg2, 1, "urgent", uid2, uid2, n8, new UserHandle(userId),
@@ -267,6 +275,7 @@
         }
         expected.add(mRecordStarredContact);
         expected.add(mRecordContact);
+        expected.add(mRecordSystemMax);
         expected.add(mRecordEmail);
         expected.add(mRecordUrgent);
         expected.add(mNoMediaSessionMedia);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
index fa294dd..3b67182 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
@@ -20,8 +20,8 @@
 import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
 import static android.content.pm.PackageManager.GET_PERMISSIONS;
-import static android.permission.PermissionManager.PERMISSION_GRANTED;
-import static android.permission.PermissionManager.PERMISSION_SOFT_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -130,13 +130,13 @@
 
     @Test
     public void testHasPermission() throws Exception {
-        when(mPmi.checkUidPermission(anyInt(), eq(Manifest.permission.POST_NOTIFICATIONS)))
+        when(mPmi.checkPostNotificationsPermissionGrantedOrLegacyAccess(anyInt()))
                 .thenReturn(PERMISSION_GRANTED);
 
         assertThat(mPermissionHelper.hasPermission(1)).isTrue();
 
-        when(mPmi.checkUidPermission(anyInt(), eq(Manifest.permission.POST_NOTIFICATIONS)))
-                .thenReturn(PERMISSION_SOFT_DENIED);
+        when(mPmi.checkPostNotificationsPermissionGrantedOrLegacyAccess(anyInt()))
+                .thenReturn(PERMISSION_DENIED);
 
         assertThat(mPermissionHelper.hasPermission(1)).isFalse();
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 30ad1f9..3298d11 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -85,7 +85,6 @@
 import static com.android.server.wm.ActivityRecord.State.STOPPED;
 import static com.android.server.wm.ActivityRecord.State.STOPPING;
 import static com.android.server.wm.ActivityTaskManagerService.INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT_MILLIS;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_INVISIBLE;
 import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE;
 import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
@@ -3114,7 +3113,7 @@
     }
 
     @Test
-    public void testInClosingAnimation_doNotHideSurface() {
+    public void testInClosingAnimation_visibilityNotCommitted_doNotHideSurface() {
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
         makeWindowVisibleAndDrawn(app);
 
@@ -3123,16 +3122,45 @@
         mDisplayContent.mClosingApps.add(app.mActivityRecord);
         mDisplayContent.prepareAppTransition(TRANSIT_CLOSE);
 
-        // Update visibility and call to remove window
-        app.mActivityRecord.commitVisibility(false, false);
+        // Remove window during transition, so it is requested to hide, but won't be committed until
+        // the transition is finished.
+        app.mActivityRecord.onRemovedFromDisplay();
+
+        assertTrue(mDisplayContent.mClosingApps.contains(app.mActivityRecord));
+        assertFalse(app.mActivityRecord.isVisibleRequested());
+        assertTrue(app.mActivityRecord.isVisible());
+        assertTrue(app.mActivityRecord.isSurfaceShowing());
+
+        // Start transition.
         app.mActivityRecord.prepareSurfaces();
 
         // Because the app is waiting for transition, it should not hide the surface.
         assertTrue(app.mActivityRecord.isSurfaceShowing());
+    }
 
-        // Ensure onAnimationFinished will callback when the closing animation is finished.
-        verify(app.mActivityRecord).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION),
-                eq(null));
+    @Test
+    public void testInClosingAnimation_visibilityCommitted_hideSurface() {
+        final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+        makeWindowVisibleAndDrawn(app);
+
+        // Put the activity in close transition.
+        mDisplayContent.mOpeningApps.clear();
+        mDisplayContent.mClosingApps.add(app.mActivityRecord);
+        mDisplayContent.prepareAppTransition(TRANSIT_CLOSE);
+
+        // Commit visibility before start transition.
+        app.mActivityRecord.commitVisibility(false, false);
+
+        assertFalse(app.mActivityRecord.isVisibleRequested());
+        assertFalse(app.mActivityRecord.isVisible());
+        assertTrue(app.mActivityRecord.isSurfaceShowing());
+
+        // Start transition.
+        app.mActivityRecord.prepareSurfaces();
+
+        // Because the app visibility has been committed before the transition start, it should hide
+        // the surface.
+        assertFalse(app.mActivityRecord.isSurfaceShowing());
     }
 
     @Test
diff --git a/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java b/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java
index 24fda17..e655013 100644
--- a/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java
+++ b/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java
@@ -69,6 +69,12 @@
     private SparseArray<SparseArray<UserBroadcastResponseStats>> mUserResponseStats =
             new SparseArray<>();
 
+    private AppStandbyInternal mAppStandby;
+
+    BroadcastResponseStatsTracker(@NonNull AppStandbyInternal appStandby) {
+        mAppStandby = appStandby;
+    }
+
     // TODO (206518114): Move all callbacks handling to a handler thread.
     void reportBroadcastDispatchEvent(int sourceUid, @NonNull String targetPackage,
             UserHandle targetUser, long idForResponseEvent,
@@ -132,8 +138,7 @@
                 if (dispatchTimestampMs >= timestampMs) {
                     continue;
                 }
-                // TODO (206518114): Make the constant configurable.
-                if (elapsedDurationMs <= 2 * 60 * 1000) {
+                if (elapsedDurationMs <= mAppStandby.getBroadcastResponseWindowDurationMs()) {
                     final BroadcastEvent broadcastEvent = broadcastEvents.valueAt(i);
                     final BroadcastResponseStats responseStats =
                             getBroadcastResponseStats(broadcastEvent);
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index e90d28a..6906f20 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -281,7 +281,7 @@
         mHandler = new H(BackgroundThread.get().getLooper());
 
         mAppStandby = mInjector.getAppStandbyController(getContext());
-        mResponseStatsTracker = new BroadcastResponseStatsTracker();
+        mResponseStatsTracker = new BroadcastResponseStatsTracker(mAppStandby);
 
         mAppTimeLimit = new AppTimeLimitController(getContext(),
                 new AppTimeLimitController.TimeLimitCallbackListener() {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index 90ccec8..a061618 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -69,6 +69,7 @@
 import com.android.server.LocalServices;
 import com.android.server.am.AssistDataRequester;
 import com.android.server.am.AssistDataRequester.AssistDataRequesterCallbacks;
+import com.android.server.power.LowPowerStandbyControllerInternal;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.uri.UriGrantsManagerInternal;
 import com.android.server.wm.ActivityAssistInfo;
@@ -89,6 +90,11 @@
     static final int POWER_BOOST_TIMEOUT_MS = Integer.parseInt(
             System.getProperty("vendor.powerhal.interaction.max", "200"));
     static final int BOOST_TIMEOUT_MS = 300;
+    /**
+     * The maximum time an app can stay on the Low Power Standby allowlist when
+     * the session is shown. There to safeguard against apps that don't call hide.
+     */
+    private static final int LOW_POWER_STANDBY_ALLOWLIST_TIMEOUT_MS = 120_000;
     // TODO: To avoid ap doesn't call hide, only 10 secs for now, need a better way to manage it
     //  in the future.
     static final int MAX_POWER_BOOST_TIMEOUT = 10_000;
@@ -124,6 +130,10 @@
             Executors.newSingleThreadScheduledExecutor();
     private final List<VisibleActivityInfo> mVisibleActivityInfos = new ArrayList<>();
     private final PowerManagerInternal mPowerManagerInternal;
+    private final LowPowerStandbyControllerInternal mLowPowerStandbyControllerInternal;
+    private final Runnable mRemoveFromLowPowerStandbyAllowlistRunnable =
+            this::removeFromLowPowerStandbyAllowlist;
+    private boolean mLowPowerStandbyAllowlisted;
     private PowerBoostSetter mSetPowerBoostRunnable;
     private final Handler mFgHandler;
 
@@ -211,6 +221,8 @@
         mIWindowManager = IWindowManager.Stub.asInterface(
                 ServiceManager.getService(Context.WINDOW_SERVICE));
         mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
+        mLowPowerStandbyControllerInternal = LocalServices.getService(
+                LowPowerStandbyControllerInternal.class);
         mAppOps = context.getSystemService(AppOpsManager.class);
         mFgHandler = FgThread.getHandler();
         mAssistDataRequester = new AssistDataRequester(mContext, mIWindowManager,
@@ -322,6 +334,15 @@
             mSetPowerBoostRunnable = new PowerBoostSetter(
                     Instant.now().plusMillis(MAX_POWER_BOOST_TIMEOUT));
             mFgHandler.post(mSetPowerBoostRunnable);
+
+            if (mLowPowerStandbyControllerInternal != null) {
+                mLowPowerStandbyControllerInternal.addToAllowlist(mCallingUid);
+                mLowPowerStandbyAllowlisted = true;
+                mFgHandler.removeCallbacks(mRemoveFromLowPowerStandbyAllowlistRunnable);
+                mFgHandler.postDelayed(mRemoveFromLowPowerStandbyAllowlistRunnable,
+                        LOW_POWER_STANDBY_ALLOWLIST_TIMEOUT_MS);
+            }
+
             mCallback.onSessionShown(this);
             return true;
         }
@@ -493,6 +514,9 @@
                 }
                 // A negative value indicates canceling previous boost.
                 mPowerManagerInternal.setPowerBoost(Boost.INTERACTION, /* durationMs */ -1);
+                if (mLowPowerStandbyControllerInternal != null) {
+                    removeFromLowPowerStandbyAllowlist();
+                }
                 mCallback.onSessionHidden(this);
             }
             if (mFullyBound) {
@@ -730,6 +754,16 @@
         }
     }
 
+    private void removeFromLowPowerStandbyAllowlist() {
+        synchronized (mLock) {
+            if (mLowPowerStandbyAllowlisted) {
+                mFgHandler.removeCallbacks(mRemoveFromLowPowerStandbyAllowlistRunnable);
+                mLowPowerStandbyControllerInternal.removeFromAllowlist(mCallingUid);
+                mLowPowerStandbyAllowlisted = false;
+            }
+        }
+    }
+
     @Override
     public void onServiceConnected(ComponentName name, IBinder service) {
         synchronized (mLock) {
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 7ba4b11..a74930b 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -8898,8 +8898,8 @@
         sDefaults.putStringArray(
                 KEY_TELEPHONY_DATA_SETUP_RETRY_RULES_STRING_ARRAY, new String[] {
                         "capabilities=eims, retry_interval=1000, maximum_retries=20",
-                        "fail_causes=8|27|28|29|30|32|33|35|50|51|111|-5|-6|65537|65538|-3|2253|"
-                                + "2254, maximum_retries=0", // No retry for those causes
+                        "fail_causes=8|27|28|29|30|32|33|35|50|51|111|-5|-6|65537|65538|-3|2252|"
+                                + "2253|2254, maximum_retries=0", // No retry for those causes
                         "capabilities=mms|supl|cbs, retry_interval=2000",
                         "capabilities=internet|enterprise|dun|ims|fota, retry_interval=2500|3000|"
                                 + "5000|10000|15000|20000|40000|60000|120000|240000|"
diff --git a/tests/ApkVerityTest/Android.bp b/tests/ApkVerityTest/Android.bp
index 4e98f42..d4fa1dd 100644
--- a/tests/ApkVerityTest/Android.bp
+++ b/tests/ApkVerityTest/Android.bp
@@ -24,14 +24,21 @@
 java_test_host {
     name: "ApkVerityTest",
     srcs: ["src/**/*.java"],
-    libs: ["tradefed", "compatibility-tradefed", "compatibility-host-util"],
+    libs: [
+        "tradefed",
+        "compatibility-tradefed",
+        "compatibility-host-util",
+    ],
     static_libs: [
         "block_device_writer_jar",
         "frameworks-base-hostutils",
     ],
-    test_suites: ["general-tests", "vts"],
-    target_required: [
-        "block_device_writer_module",
+    test_suites: [
+        "general-tests",
+        "vts",
+    ],
+    data_device_bins: [
+        "block_device_writer",
     ],
     data: [
         ":ApkVerityTestCertDer",
diff --git a/tests/ApkVerityTest/block_device_writer/Android.bp b/tests/ApkVerityTest/block_device_writer/Android.bp
index 0b5f0f6..e5d009d 100644
--- a/tests/ApkVerityTest/block_device_writer/Android.bp
+++ b/tests/ApkVerityTest/block_device_writer/Android.bp
@@ -24,12 +24,7 @@
 }
 
 cc_test {
-    // Depending on how the test runs, the executable may be uploaded to different location.
-    // Before the bug in the file pusher is fixed, workaround by making the name unique.
-    // See b/124718249#comment12.
-    name: "block_device_writer_module",
-    stem: "block_device_writer",
-
+    name: "block_device_writer",
     srcs: ["block_device_writer.cpp"],
     cflags: [
         "-D_FILE_OFFSET_BITS=64",
@@ -38,31 +33,25 @@
         "-Wextra",
         "-g",
     ],
-    shared_libs: ["libbase", "libutils"],
-    // For some reasons, cuttlefish (x86) uses x86_64 test suites for testing. Unfortunately, when
-    // the uploader does not pick up the executable from correct output location. The following
-    // workaround allows the test to:
-    //  * upload the 32-bit exectuable for both 32 and 64 bits devices to use
-    //  * refer to the same executable name in Java
-    //  * no need to force the Java test to be archiecture specific.
-    //
-    // See b/145573317 for details.
-    multilib: {
-        lib32: {
-            suffix: "",
-        },
-        lib64: {
-            suffix: "64",  // not really used
-        },
-    },
+    shared_libs: [
+        "libbase",
+        "libutils",
+    ],
+    compile_multilib: "first",
 
     auto_gen_config: false,
-    test_suites: ["general-tests", "pts", "vts"],
+    test_suites: [
+        "general-tests",
+        "vts",
+    ],
     gtest: false,
 }
 
 java_library_host {
     name: "block_device_writer_jar",
     srcs: ["src/**/*.java"],
-    libs: ["tradefed", "junit"],
+    libs: [
+        "tradefed",
+        "junit",
+    ],
 }
diff --git a/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java b/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java
index 5c2c15b..730daf3 100644
--- a/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java
+++ b/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java
@@ -32,7 +32,7 @@
  * <p>To use this class, please push block_device_writer binary to /data/local/tmp.
  * 1. In Android.bp, add:
  * <pre>
- *     target_required: ["block_device_writer_module"],
+ *      data_device_bins: ["block_device_writer"],
  * </pre>
  * 2. In AndroidText.xml, add:
  * <pre>